mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
First step in removing the coupling of Editor and Buffer. Editor should get all information about the active buffer from the activeEditSession.
1567 lines
71 KiB
CoffeeScript
1567 lines
71 KiB
CoffeeScript
RootView = require 'root-view'
|
|
Buffer = require 'buffer'
|
|
Editor = require 'editor'
|
|
Range = require 'range'
|
|
Project = require 'project'
|
|
$ = require 'jquery'
|
|
{$$} = require 'space-pen'
|
|
_ = require 'underscore'
|
|
fs = require 'fs'
|
|
|
|
describe "Editor", ->
|
|
[rootView, buffer, editor, cachedLineHeight] = []
|
|
|
|
getLineHeight = ->
|
|
return cachedLineHeight if cachedLineHeight?
|
|
editorForMeasurement = new Editor()
|
|
editorForMeasurement.attachToDom()
|
|
cachedLineHeight = editorForMeasurement.lineHeight
|
|
editorForMeasurement.remove()
|
|
cachedLineHeight
|
|
|
|
beforeEach ->
|
|
rootView = new RootView(require.resolve('fixtures/sample.js'))
|
|
project = rootView.project
|
|
editor = rootView.activeEditor()
|
|
buffer = editor.buffer
|
|
|
|
editor.attachToDom = ({ heightInLines } = {}) ->
|
|
heightInLines ?= this.buffer.getLineCount()
|
|
this.height(getLineHeight() * heightInLines)
|
|
$('#jasmine-content').append(this)
|
|
|
|
editor.lineOverdraw = 2
|
|
editor.setAutoIndent(false)
|
|
editor.enableKeymap()
|
|
editor.isFocused = true
|
|
|
|
afterEach ->
|
|
editor.remove()
|
|
|
|
describe "construction", ->
|
|
it "assigns an empty buffer and correctly handles text input (regression coverage)", ->
|
|
editor = new Editor
|
|
editor.attachToDom()
|
|
expect(editor.buffer.getPath()).toBeUndefined()
|
|
expect(editor.renderedLines.find('.line').length).toBe 1
|
|
editor.insertText('x')
|
|
expect(editor.renderedLines.find('.line').length).toBe 1
|
|
|
|
describe ".copy()", ->
|
|
it "builds a new editor with the same edit sessions, cursor position, and scroll position as the receiver", ->
|
|
rootView.attachToDom()
|
|
rootView.height(8 * editor.lineHeight)
|
|
rootView.width(50 * editor.charWidth)
|
|
|
|
editor.setCursorScreenPosition([5, 20])
|
|
advanceClock()
|
|
editor.scrollTop(1.5 * editor.lineHeight)
|
|
editor.scrollView.scrollLeft(44)
|
|
|
|
# prove this test covers serialization and deserialization
|
|
spyOn(editor, 'serialize').andCallThrough()
|
|
spyOn(Editor, 'deserialize').andCallThrough()
|
|
|
|
newEditor = editor.copy()
|
|
expect(editor.serialize).toHaveBeenCalled()
|
|
expect(Editor.deserialize).toHaveBeenCalled()
|
|
|
|
|
|
expect(newEditor.buffer).toBe editor.buffer
|
|
expect(newEditor.getCursorScreenPosition()).toEqual editor.getCursorScreenPosition()
|
|
expect(newEditor.editSessions[0]).toEqual(editor.editSessions[0])
|
|
expect(newEditor.editSessions[0]).not.toBe(editor.editSessions[0])
|
|
|
|
newEditor.height(editor.height())
|
|
newEditor.width(editor.width())
|
|
rootView.remove()
|
|
newEditor.attachToDom()
|
|
expect(newEditor.scrollTop()).toBe editor.scrollTop()
|
|
expect(newEditor.scrollView.scrollLeft()).toBe 44
|
|
newEditor.remove()
|
|
|
|
describe "when the editor is attached to the dom", ->
|
|
it "calculates line height and char width and updates the pixel position of the cursor", ->
|
|
expect(editor.lineHeight).toBeNull()
|
|
expect(editor.charWidth).toBeNull()
|
|
editor.setCursorScreenPosition(row: 2, column: 2)
|
|
|
|
editor.attachToDom()
|
|
|
|
expect(editor.lineHeight).not.toBeNull()
|
|
expect(editor.charWidth).not.toBeNull()
|
|
expect(editor.find('.cursor').offset()).toEqual pagePixelPositionForPoint(editor, [2, 2])
|
|
|
|
it "is focused", ->
|
|
editor.attachToDom()
|
|
expect(editor).toMatchSelector ":has(:focus)"
|
|
|
|
it "unsubscribes from the buffer when it is removed from the dom", ->
|
|
buffer = new Buffer
|
|
previousSubscriptionCount = buffer.subscriptionCount()
|
|
|
|
editor.attachToDom()
|
|
editor.setBuffer(buffer)
|
|
|
|
expect(buffer.subscriptionCount()).toBeGreaterThan previousSubscriptionCount
|
|
expect($('.editor')).toExist()
|
|
editor.remove()
|
|
expect(buffer.subscriptionCount()).toBe previousSubscriptionCount
|
|
expect($('.editor')).not.toExist()
|
|
|
|
describe "when the editor recieves focus", ->
|
|
it "focuses the hidden input", ->
|
|
editor.attachToDom()
|
|
editor.focus()
|
|
expect(editor).not.toMatchSelector ':focus'
|
|
expect(editor.hiddenInput).toMatchSelector ':focus'
|
|
|
|
describe "when the hidden input is focused / unfocused", ->
|
|
it "assigns the isFocused flag on the editor and also adds/removes the .focused css class", ->
|
|
editor.attachToDom()
|
|
editor.isFocused = false
|
|
editor.hiddenInput.focus()
|
|
expect(editor.isFocused).toBeTruthy()
|
|
expect(editor).toHaveClass('focused')
|
|
|
|
editor.hiddenInput.focusout()
|
|
expect(editor.isFocused).toBeFalsy()
|
|
expect(editor).not.toHaveClass('focused')
|
|
|
|
describe ".remove()", ->
|
|
it "removes subscriptions from all edit session buffers", ->
|
|
otherBuffer = new Buffer(require.resolve('fixtures/sample.txt'))
|
|
expect(buffer.subscriptionCount()).toBeGreaterThan 1
|
|
|
|
editor.setBuffer(otherBuffer)
|
|
expect(otherBuffer.subscriptionCount()).toBeGreaterThan 1
|
|
|
|
editor.remove()
|
|
expect(buffer.subscriptionCount()).toBe 1
|
|
expect(otherBuffer.subscriptionCount()).toBe 1
|
|
|
|
describe "when 'close' is triggered", ->
|
|
it "closes active edit session and loads next edit session", ->
|
|
editor.setBuffer(new Buffer())
|
|
spyOn(editor, "remove")
|
|
editor.trigger "close"
|
|
expect(editor.remove).not.toHaveBeenCalled()
|
|
expect(editor.buffer).toBe buffer
|
|
|
|
it "calls remove on the editor if there is one edit session and mini is false", ->
|
|
originalBuffer = editor.buffer
|
|
expect(editor.mini).toBeFalsy()
|
|
expect(editor.editSessions.length).toBe 1
|
|
spyOn(editor, 'remove')
|
|
editor.trigger 'close'
|
|
expect(editor.remove).toHaveBeenCalled()
|
|
|
|
editor.remove()
|
|
editor = new Editor(mini: true)
|
|
spyOn(editor, 'remove')
|
|
editor.trigger 'close'
|
|
expect(editor.remove).not.toHaveBeenCalled()
|
|
|
|
describe "when buffer is modified", ->
|
|
it "triggers alert and does not close session", ->
|
|
spyOn(editor, 'remove')
|
|
spyOn($native, 'alert')
|
|
editor.insertText("I AM CHANGED!")
|
|
editor.trigger "close"
|
|
expect(editor.remove).not.toHaveBeenCalled()
|
|
expect($native.alert).toHaveBeenCalled()
|
|
|
|
describe ".setBuffer(buffer)", ->
|
|
otherBuffer = null
|
|
|
|
beforeEach ->
|
|
otherBuffer = new Buffer
|
|
|
|
describe "when the buffer wasn't previously assigned to this editor", ->
|
|
it "creates a new EditSession for it", ->
|
|
editor.setBuffer(otherBuffer)
|
|
expect(editor.activeEditSession.buffer).toBe otherBuffer
|
|
|
|
describe "when the buffer was previously assigned to this editor", ->
|
|
it "restores the previous edit session associated with the buffer", ->
|
|
previousEditSession = editor.activeEditSession
|
|
|
|
editor.setBuffer(otherBuffer)
|
|
expect(editor.activeEditSession).not.toBe previousEditSession
|
|
|
|
editor.setBuffer(buffer)
|
|
expect(editor.activeEditSession).toBe previousEditSession
|
|
|
|
it "unsubscribes from the previously assigned buffer", ->
|
|
editor.setBuffer(otherBuffer)
|
|
|
|
previousSubscriptionCount = buffer.subscriptionCount()
|
|
|
|
editor.setBuffer(buffer)
|
|
editor.setBuffer(otherBuffer)
|
|
|
|
expect(buffer.subscriptionCount()).toBe previousSubscriptionCount
|
|
|
|
it "handles buffer manipulation correctly after switching to a new buffer", ->
|
|
editor.attachToDom()
|
|
editor.insertText("abc\n")
|
|
expect(editor.lineElementForScreenRow(0).text()).toBe 'abc'
|
|
|
|
editor.setBuffer(otherBuffer)
|
|
expect(editor.lineElementForScreenRow(0).html()).toBe ' '
|
|
|
|
editor.insertText("def\n")
|
|
expect(editor.lineElementForScreenRow(0).text()).toBe 'def'
|
|
|
|
describe "switching edit sessions", ->
|
|
[buffer0, buffer1, buffer2] = []
|
|
[session0, session1, session2] = []
|
|
|
|
beforeEach ->
|
|
buffer0 = buffer
|
|
session0 = editor.activeEditSession
|
|
|
|
buffer1 = new Buffer(require.resolve('fixtures/sample.txt'))
|
|
editor.setBuffer(buffer1)
|
|
session1 = editor.activeEditSession
|
|
|
|
buffer2 = new Buffer(require.resolve('fixtures/two-hundred.txt'))
|
|
editor.setBuffer(buffer2)
|
|
session2 = editor.activeEditSession
|
|
|
|
describe ".setActiveEditSessionIndex(index)", ->
|
|
it "restores the buffer, cursors, selections, and scroll position of the edit session associated with the index", ->
|
|
editor.attachToDom(heightInLines: 10)
|
|
editor.setSelectedBufferRange([[40, 0], [43, 1]])
|
|
expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
|
|
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
|
editor.scrollTop(750)
|
|
expect(editor.scrollTop()).toBe 750
|
|
|
|
editor.setActiveEditSessionIndex(0)
|
|
expect(editor.buffer).toBe buffer0
|
|
|
|
editor.setActiveEditSessionIndex(2)
|
|
expect(editor.buffer).toBe buffer2
|
|
expect(editor.getCursorScreenPosition()).toEqual [43, 1]
|
|
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
|
|
expect(editor.scrollTop()).toBe 750
|
|
expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
|
|
expect(editor.getSelectionView().find('.selection')).toExist()
|
|
|
|
editor.setActiveEditSessionIndex(0)
|
|
editor.activeEditSession.selectToEndOfLine()
|
|
expect(editor.getSelectionView().find('.selection')).toExist()
|
|
|
|
describe ".loadNextEditSession()", ->
|
|
it "loads the next editor state and wraps to beginning when end is reached", ->
|
|
expect(editor.activeEditSession).toBe session2
|
|
editor.loadNextEditSession()
|
|
expect(editor.activeEditSession).toBe session0
|
|
editor.loadNextEditSession()
|
|
expect(editor.activeEditSession).toBe session1
|
|
editor.loadNextEditSession()
|
|
expect(editor.activeEditSession).toBe session2
|
|
|
|
describe ".loadPreviousEditSession()", ->
|
|
it "loads the next editor state and wraps to beginning when end is reached", ->
|
|
expect(editor.activeEditSession).toBe session2
|
|
editor.loadPreviousEditSession()
|
|
expect(editor.activeEditSession).toBe session1
|
|
editor.loadPreviousEditSession()
|
|
expect(editor.activeEditSession).toBe session0
|
|
editor.loadPreviousEditSession()
|
|
expect(editor.activeEditSession).toBe session2
|
|
|
|
describe ".save()", ->
|
|
describe "when the current buffer has a path", ->
|
|
tempFilePath = null
|
|
|
|
beforeEach ->
|
|
tempFilePath = '/tmp/atom-temp.txt'
|
|
editor.setBuffer new Buffer(tempFilePath)
|
|
expect(editor.buffer.getPath()).toBe tempFilePath
|
|
|
|
afterEach ->
|
|
expect(fs.remove(tempFilePath))
|
|
|
|
it "saves the current buffer to disk", ->
|
|
editor.buffer.setText 'Edited!'
|
|
expect(fs.exists(tempFilePath)).toBeFalsy()
|
|
|
|
editor.save()
|
|
|
|
expect(fs.exists(tempFilePath)).toBeTruthy()
|
|
expect(fs.read(tempFilePath)).toBe 'Edited!'
|
|
|
|
describe "when the current buffer has no path", ->
|
|
selectedFilePath = null
|
|
beforeEach ->
|
|
editor.setBuffer new Buffer()
|
|
expect(editor.buffer.getPath()).toBeUndefined()
|
|
editor.buffer.setText 'Save me to a new path'
|
|
spyOn($native, 'saveDialog').andCallFake -> selectedFilePath
|
|
|
|
it "presents a 'save as' dialog", ->
|
|
editor.save()
|
|
expect($native.saveDialog).toHaveBeenCalled()
|
|
|
|
describe "when a path is chosen", ->
|
|
it "saves the buffer to the chosen path", ->
|
|
selectedFilePath = '/tmp/temp.txt'
|
|
|
|
editor.save()
|
|
|
|
expect(fs.exists(selectedFilePath)).toBeTruthy()
|
|
expect(fs.read(selectedFilePath)).toBe 'Save me to a new path'
|
|
|
|
describe "when dialog is cancelled", ->
|
|
it "does not save the buffer", ->
|
|
selectedFilePath = null
|
|
editor.save()
|
|
expect(fs.exists(selectedFilePath)).toBeFalsy()
|
|
|
|
describe ".scrollTop(n)", ->
|
|
beforeEach ->
|
|
editor.attachToDom(heightInLines: 5)
|
|
expect(editor.verticalScrollbar.scrollTop()).toBe 0
|
|
|
|
describe "when called with a scroll top argument", ->
|
|
it "sets the scrollTop of the vertical scrollbar and sets scrollTop on the line numbers and lines", ->
|
|
editor.scrollTop(100)
|
|
expect(editor.verticalScrollbar.scrollTop()).toBe 100
|
|
expect(editor.scrollView.scrollTop()).toBe 0
|
|
expect(editor.renderedLines.css('top')).toBe "-100px"
|
|
expect(editor.gutter.scrollTop()).toBe 100
|
|
|
|
editor.scrollTop(120)
|
|
expect(editor.verticalScrollbar.scrollTop()).toBe 120
|
|
expect(editor.scrollView.scrollTop()).toBe 0
|
|
expect(editor.renderedLines.css('top')).toBe "-120px"
|
|
expect(editor.gutter.scrollTop()).toBe 120
|
|
|
|
it "does not allow negative scrollTops to be assigned", ->
|
|
editor.scrollTop(-100)
|
|
expect(editor.scrollTop()).toBe 0
|
|
|
|
it "doesn't do anything if the scrollTop hasn't changed", ->
|
|
editor.scrollTop(100)
|
|
spyOn(editor.verticalScrollbar, 'scrollTop')
|
|
spyOn(editor.renderedLines, 'css')
|
|
spyOn(editor.gutter.lineNumbers, 'css')
|
|
|
|
editor.scrollTop(100)
|
|
expect(editor.verticalScrollbar.scrollTop).not.toHaveBeenCalled()
|
|
expect(editor.renderedLines.css).not.toHaveBeenCalled()
|
|
expect(editor.gutter.lineNumbers.css).not.toHaveBeenCalled()
|
|
|
|
describe "when the 'adjustVerticalScrollbar' option is false (defaults to true)", ->
|
|
it "doesn't adjust the scrollTop of the vertical scrollbar", ->
|
|
editor.scrollTop(100, adjustVerticalScrollbar: false)
|
|
expect(editor.verticalScrollbar.scrollTop()).toBe 0
|
|
expect(editor.renderedLines.css('top')).toBe "-100px"
|
|
expect(editor.gutter.scrollTop()).toBe 100
|
|
|
|
describe "when called with no argument", ->
|
|
it "returns the last assigned value or 0 if none has been assigned", ->
|
|
expect(editor.scrollTop()).toBe 0
|
|
editor.scrollTop(50)
|
|
expect(editor.scrollTop()).toBe 50
|
|
|
|
describe "split methods", ->
|
|
describe "when inside a pane", ->
|
|
fakePane = null
|
|
beforeEach ->
|
|
fakePane = { splitUp: jasmine.createSpy('splitUp').andReturn({}), remove: -> }
|
|
spyOn(editor, 'pane').andReturn(fakePane)
|
|
|
|
it "calls the corresponding split method on the containing pane with a copy of the editor", ->
|
|
editor.splitUp()
|
|
expect(fakePane.splitUp).toHaveBeenCalled()
|
|
[editorCopy] = fakePane.splitUp.argsForCall[0]
|
|
expect(editorCopy.serialize()).toEqual editor.serialize()
|
|
expect(editorCopy).not.toBe editor
|
|
editorCopy.remove()
|
|
|
|
describe "when not inside a pane", ->
|
|
it "does not split the editor, but doesn't throw an exception", ->
|
|
editor.splitUp()
|
|
editor.splitDown()
|
|
editor.splitLeft()
|
|
editor.splitRight()
|
|
|
|
describe "editor-open event", ->
|
|
it 'only triggers an editor-open event when it is first added to the DOM', ->
|
|
openHandler = jasmine.createSpy('openHandler')
|
|
editor.on 'editor-open', openHandler
|
|
|
|
editor.simulateDomAttachment()
|
|
expect(openHandler).toHaveBeenCalled()
|
|
[event, eventEditor] = openHandler.argsForCall[0]
|
|
expect(eventEditor).toBe editor
|
|
|
|
openHandler.reset()
|
|
editor.simulateDomAttachment()
|
|
expect(openHandler).not.toHaveBeenCalled()
|
|
|
|
describe "editor-path-change event", ->
|
|
it "emits event when buffer's path is changed", ->
|
|
eventHandler = jasmine.createSpy('eventHandler')
|
|
editor.on 'editor-path-change', eventHandler
|
|
editor.buffer.setPath("moo.text")
|
|
|
|
it "emits event when editor receives a new buffer", ->
|
|
eventHandler = jasmine.createSpy('eventHandler')
|
|
editor.on 'editor-path-change', eventHandler
|
|
editor.setBuffer(new Buffer("something.txt"))
|
|
expect(eventHandler).toHaveBeenCalled()
|
|
|
|
it "stops listening to events on previously set buffers", ->
|
|
eventHandler = jasmine.createSpy('eventHandler')
|
|
oldBuffer = editor.buffer
|
|
editor.on 'editor-path-change', eventHandler
|
|
|
|
editor.setBuffer(new Buffer("something.txt"))
|
|
expect(eventHandler).toHaveBeenCalled()
|
|
|
|
eventHandler.reset()
|
|
oldBuffer.setPath("bad.txt")
|
|
expect(eventHandler).not.toHaveBeenCalled()
|
|
|
|
eventHandler.reset()
|
|
editor.buffer.setPath("new.txt")
|
|
expect(eventHandler).toHaveBeenCalled()
|
|
|
|
describe "font size", ->
|
|
it "sets the initial font size based on the value assigned to the root view", ->
|
|
rootView.setFontSize(20)
|
|
rootView.simulateDomAttachment()
|
|
newEditor = editor.splitRight()
|
|
expect(editor.css('font-size')).toBe '20px'
|
|
expect(newEditor.css('font-size')).toBe '20px'
|
|
|
|
describe "when the font size changes on the view", ->
|
|
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
|
|
rootView.attachToDom()
|
|
rootView.setFontSize(10)
|
|
lineHeightBefore = editor.lineHeight
|
|
charWidthBefore = editor.charWidth
|
|
editor.setCursorScreenPosition [5, 5]
|
|
|
|
rootView.setFontSize(30)
|
|
expect(editor.css('font-size')).toBe '30px'
|
|
expect(editor.lineHeight).toBeGreaterThan lineHeightBefore
|
|
expect(editor.charWidth).toBeGreaterThan charWidthBefore
|
|
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 5 * editor.charWidth }
|
|
|
|
# ensure we clean up font size subscription
|
|
editor.trigger('close')
|
|
rootView.setFontSize(22)
|
|
expect(editor.css('font-size')).toBe '30px'
|
|
|
|
it "updates the gutter width and font size", ->
|
|
rootView.attachToDom()
|
|
originalFontSize = rootView.getFontSize()
|
|
originalGutterWidth = editor.gutter.width()
|
|
|
|
rootView.setFontSize(originalFontSize * 4)
|
|
expect(editor.gutter.css('font-size')).toBe "#{originalFontSize * 4}px"
|
|
expect(editor.gutter.width()).toBe(originalGutterWidth * 4)
|
|
|
|
it "updates lines if there are unrendered lines", ->
|
|
editor.attachToDom(heightInLines: 5)
|
|
originalLineCount = editor.renderedLines.find(".line").length
|
|
expect(originalLineCount).toBeGreaterThan 0
|
|
editor.setFontSize(10)
|
|
expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount
|
|
|
|
describe "mouse events", ->
|
|
beforeEach ->
|
|
editor.attachToDom()
|
|
editor.css(position: 'absolute', top: 10, left: 10)
|
|
|
|
describe "single-click", ->
|
|
it "re-positions the cursor to the clicked row / column", ->
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
|
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 10])
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)
|
|
|
|
describe "when the lines are scrolled to the right", ->
|
|
it "re-positions the cursor on the clicked location", ->
|
|
setEditorWidthInChars(editor, 30)
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 30]) # scrolls lines to the right
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 50])
|
|
expect(editor.getCursorBufferPosition()).toEqual(row: 3, column: 50)
|
|
|
|
describe "double-click", ->
|
|
it "selects the word under the cursor", ->
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 1})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 2})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
expect(editor.getSelectedText()).toBe "quicksort"
|
|
|
|
describe "triple/quardruple/etc-click", ->
|
|
it "selects the line under the cursor", ->
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
|
|
|
|
# Triple click
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 1})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 2})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 3})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
expect(editor.getSelectedText()).toBe " var sort = function(items) {"
|
|
|
|
# Quad click
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 1})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 2})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 3})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 4})
|
|
editor.renderedLines.trigger 'mouseup'
|
|
expect(editor.getSelectedText()).toBe " if (items.length <= 1) return items;"
|
|
|
|
describe "shift-click", ->
|
|
it "selects from the cursor's current location to the clicked location", ->
|
|
editor.setCursorScreenPosition([4, 7])
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true)
|
|
expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 24]]
|
|
|
|
describe "shift-double-click", ->
|
|
it "expands the selection to include the double-clicked word", ->
|
|
editor.setCursorScreenPosition([4, 7])
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
|
|
editor.renderedLines.trigger 'mouseup'
|
|
expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 27]]
|
|
|
|
describe "shift-triple-click", ->
|
|
it "expands the selection to include the triple-clicked line", ->
|
|
editor.setCursorScreenPosition([4, 7])
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
|
|
editor.renderedLines.trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 3 })
|
|
editor.renderedLines.trigger 'mouseup'
|
|
expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 30]]
|
|
|
|
describe "meta-click", ->
|
|
it "places an additional cursor", ->
|
|
editor.attachToDom()
|
|
setEditorHeightInLines(editor, 5)
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 0])
|
|
editor.scrollTop(editor.lineHeight * 6)
|
|
|
|
spyOn(editor, "scrollTo").andCallThrough()
|
|
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 0], metaKey: true)
|
|
expect(editor.scrollTo.callCount).toBe 1
|
|
|
|
[cursor1, cursor2] = editor.getCursorViews()
|
|
expect(cursor1.position()).toEqual(top: 3 * editor.lineHeight, left: 0)
|
|
expect(cursor1.getBufferPosition()).toEqual [3, 0]
|
|
expect(cursor2.position()).toEqual(top: 6 * editor.lineHeight, left: 0)
|
|
expect(cursor2.getBufferPosition()).toEqual [6, 0]
|
|
|
|
describe "click and drag", ->
|
|
it "creates a selection from the initial click to mouse cursor's location ", ->
|
|
editor.attachToDom()
|
|
editor.css(position: 'absolute', top: 10, left: 10)
|
|
|
|
# start
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 10])
|
|
|
|
# moving changes selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 10})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
# mouse up may occur outside of editor, but still need to halt selection
|
|
$(document).trigger 'mouseup'
|
|
|
|
# moving after mouse up should not change selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 8])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 10})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
describe "double-click and drag", ->
|
|
it "creates a selection from the word underneath an initial double click to mouse's new location ", ->
|
|
editor.attachToDom()
|
|
editor.css(position: 'absolute', top: 10, left: 10)
|
|
|
|
# double click
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 7], originalEvent: {detail: 1})
|
|
$(document).trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 7], originalEvent: {detail: 2})
|
|
|
|
# moving changes selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 4})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
# mouse up may occur outside of editor, but still need to halt selection
|
|
$(document).trigger 'mouseup'
|
|
|
|
# moving after mouse up should not change selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 8])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 4})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
describe "trip-click and drag", ->
|
|
it "creates a selection from the line underneath an initial triple click to mouse's new location ", ->
|
|
editor.attachToDom()
|
|
editor.css(position: 'absolute', top: 10, left: 10)
|
|
|
|
# double click
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 7], originalEvent: {detail: 1})
|
|
$(document).trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 7], originalEvent: {detail: 2})
|
|
$(document).trigger 'mouseup'
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 7], originalEvent: {detail: 3})
|
|
|
|
# moving changes selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 0})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
# mouse up may occur outside of editor, but still need to halt selection
|
|
$(document).trigger 'mouseup'
|
|
|
|
# moving after mouse up should not change selection
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 8])
|
|
|
|
range = editor.getSelection().getScreenRange()
|
|
expect(range.start).toEqual({row: 4, column: 0})
|
|
expect(range.end).toEqual({row: 5, column: 27})
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
|
|
|
|
describe "meta-click and drag", ->
|
|
it "adds an additional selection", ->
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 10])
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
|
|
editor.renderedLines.trigger 'mouseup'
|
|
|
|
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 10], metaKey: true)
|
|
editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 27], metaKey: true)
|
|
editor.renderedLines.trigger 'mouseup'
|
|
|
|
selections = editor.getSelections()
|
|
expect(selections.length).toBe 2
|
|
[selection1, selection2] = selections
|
|
expect(selection1.getScreenRange()).toEqual [[4, 10], [5, 27]]
|
|
expect(selection2.getScreenRange()).toEqual [[6, 10], [8, 27]]
|
|
|
|
describe "when text input events are triggered on the hidden input element", ->
|
|
it "inserts the typed character at the cursor position, both in the buffer and the pre element", ->
|
|
editor.attachToDom()
|
|
editor.setCursorScreenPosition(row: 1, column: 6)
|
|
|
|
expect(buffer.lineForRow(1).charAt(6)).not.toBe 'q'
|
|
|
|
editor.hiddenInput.textInput 'q'
|
|
|
|
expect(buffer.lineForRow(1).charAt(6)).toBe 'q'
|
|
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 7)
|
|
expect(editor.renderedLines.find('.line:eq(1)')).toHaveText buffer.lineForRow(1)
|
|
|
|
describe "selection rendering", ->
|
|
[charWidth, lineHeight, selection, selectionView] = []
|
|
|
|
beforeEach ->
|
|
editor.attachToDom()
|
|
editor.width(500)
|
|
{ charWidth, lineHeight } = editor
|
|
selection = editor.getSelection()
|
|
selectionView = editor.getSelectionView()
|
|
|
|
describe "when a selection is added", ->
|
|
it "adds a selection view for it with the proper regions", ->
|
|
editor.activeEditSession.addSelectionForBufferRange([[2, 7], [2, 25]])
|
|
selectionViews = editor.getSelectionViews()
|
|
expect(selectionViews.length).toBe 2
|
|
expect(selectionViews[1].regions.length).toBe 1
|
|
region = selectionViews[1].regions[0]
|
|
expect(region.position().top).toBe(2 * lineHeight)
|
|
expect(region.position().left).toBe(7 * charWidth)
|
|
expect(region.height()).toBe lineHeight
|
|
expect(region.width()).toBe((25 - 7) * charWidth)
|
|
|
|
describe "when a selection changes", ->
|
|
describe "when the selection is within a single line", ->
|
|
it "covers the selection's range with a single region", ->
|
|
selection.setBufferRange(new Range({row: 2, column: 7}, {row: 2, column: 25}))
|
|
|
|
expect(selectionView.regions.length).toBe 1
|
|
region = selectionView.regions[0]
|
|
expect(region.position().top).toBe(2 * lineHeight)
|
|
expect(region.position().left).toBe(7 * charWidth)
|
|
expect(region.height()).toBe lineHeight
|
|
expect(region.width()).toBe((25 - 7) * charWidth)
|
|
|
|
describe "when the selection spans 2 lines", ->
|
|
it "covers the selection's range with 2 regions", ->
|
|
selection.setBufferRange(new Range({row: 2, column: 7}, {row: 3, column: 25}))
|
|
|
|
expect(selectionView.regions.length).toBe 2
|
|
|
|
region1 = selectionView.regions[0]
|
|
expect(region1.position().top).toBe(2 * lineHeight)
|
|
expect(region1.position().left).toBe(7 * charWidth)
|
|
expect(region1.height()).toBe lineHeight
|
|
expect(region1.width()).toBe(editor.renderedLines.width() - region1.position().left)
|
|
|
|
region2 = selectionView.regions[1]
|
|
expect(region2.position().top).toBe(3 * lineHeight)
|
|
expect(region2.position().left).toBe(0)
|
|
expect(region2.height()).toBe lineHeight
|
|
expect(region2.width()).toBe(25 * charWidth)
|
|
|
|
describe "when the selection spans more than 2 lines", ->
|
|
it "covers the selection's range with 3 regions", ->
|
|
selection.setBufferRange(new Range({row: 2, column: 7}, {row: 6, column: 25}))
|
|
|
|
expect(selectionView.regions.length).toBe 3
|
|
|
|
region1 = selectionView.regions[0]
|
|
expect(region1.position().top).toBe(2 * lineHeight)
|
|
expect(region1.position().left).toBe(7 * charWidth)
|
|
expect(region1.height()).toBe lineHeight
|
|
expect(region1.width()).toBe(editor.renderedLines.width() - region1.position().left)
|
|
|
|
region2 = selectionView.regions[1]
|
|
expect(region2.position().top).toBe(3 * lineHeight)
|
|
expect(region2.position().left).toBe(0)
|
|
expect(region2.height()).toBe(3 * lineHeight)
|
|
expect(region2.width()).toBe(editor.renderedLines.width())
|
|
|
|
# resizes with the editor
|
|
expect(editor.width()).toBeLessThan(800)
|
|
editor.width(800)
|
|
expect(region2.width()).toBe(editor.renderedLines.width())
|
|
|
|
region3 = selectionView.regions[2]
|
|
expect(region3.position().top).toBe(6 * lineHeight)
|
|
expect(region3.position().left).toBe(0)
|
|
expect(region3.height()).toBe lineHeight
|
|
expect(region3.width()).toBe(25 * charWidth)
|
|
|
|
it "clears previously drawn regions before creating new ones", ->
|
|
selection.setBufferRange(new Range({row: 2, column: 7}, {row: 4, column: 25}))
|
|
expect(selectionView.regions.length).toBe 3
|
|
expect(selectionView.find('.selection').length).toBe 3
|
|
|
|
selectionView.updateAppearance()
|
|
expect(selectionView.regions.length).toBe 3
|
|
expect(selectionView.find('.selection').length).toBe 3
|
|
|
|
describe "when a selection merges with another selection", ->
|
|
it "removes the merged selection view", ->
|
|
editSession = editor.activeEditSession
|
|
editSession.setCursorScreenPosition([4, 10])
|
|
editSession.selectToScreenPosition([5, 27])
|
|
editSession.addCursorAtScreenPosition([3, 10])
|
|
editSession.selectToScreenPosition([6, 27])
|
|
|
|
expect(editor.getSelectionViews().length).toBe 1
|
|
expect(editor.find('.selection').length).toBe 3
|
|
|
|
describe "cursor rendering", ->
|
|
describe "when the cursor moves", ->
|
|
charWidth = null
|
|
|
|
beforeEach ->
|
|
editor.attachToDom()
|
|
editor.vScrollMargin = 3
|
|
editor.hScrollMargin = 5
|
|
{charWidth} = editor
|
|
|
|
it "repositions the cursor's view on screen", ->
|
|
editor.setCursorScreenPosition(row: 2, column: 2)
|
|
expect(editor.getCursorView().position()).toEqual(top: 2 * editor.lineHeight, left: 2 * editor.charWidth)
|
|
|
|
it "removes the idle class while moving, then adds it back when it stops", ->
|
|
cursorView = editor.getCursorView()
|
|
advanceClock(200)
|
|
|
|
expect(cursorView).toHaveClass 'idle'
|
|
editor.setCursorScreenPosition([1, 2])
|
|
expect(cursorView).not.toHaveClass 'idle'
|
|
|
|
window.advanceClock(200)
|
|
expect(cursorView).toHaveClass 'idle'
|
|
|
|
editor.setCursorScreenPosition([1, 3])
|
|
advanceClock(100)
|
|
|
|
editor.setCursorScreenPosition([1, 4])
|
|
advanceClock(100)
|
|
expect(cursorView).not.toHaveClass 'idle'
|
|
|
|
advanceClock(100)
|
|
expect(cursorView).toHaveClass 'idle'
|
|
|
|
describe "auto-scrolling", ->
|
|
it "only auto-scrolls when the last cursor is moved", ->
|
|
editor.setCursorBufferPosition([11,0])
|
|
editor.addCursorAtBufferPosition([6,50])
|
|
[cursor1, cursor2] = editor.getCursors()
|
|
|
|
spyOn(editor, 'scrollTo')
|
|
cursor1.setScreenPosition([10, 10])
|
|
expect(editor.scrollTo).not.toHaveBeenCalled()
|
|
|
|
cursor2.setScreenPosition([11, 11])
|
|
expect(editor.scrollTo).toHaveBeenCalled()
|
|
|
|
describe "when the last cursor exceeds the upper or lower scroll margins", ->
|
|
describe "when the editor is taller than twice the vertical scroll margin", ->
|
|
it "sets the scrollTop so the cursor remains within the scroll margin", ->
|
|
setEditorHeightInLines(editor, 10)
|
|
|
|
_.times 6, -> editor.moveCursorDown()
|
|
expect(editor.scrollTop()).toBe(0)
|
|
|
|
editor.moveCursorDown()
|
|
expect(editor.scrollTop()).toBe(editor.lineHeight)
|
|
|
|
editor.moveCursorDown()
|
|
expect(editor.scrollTop()).toBe(editor.lineHeight * 2)
|
|
|
|
_.times 3, -> editor.moveCursorUp()
|
|
|
|
editor.moveCursorUp()
|
|
expect(editor.scrollTop()).toBe(editor.lineHeight)
|
|
|
|
editor.moveCursorUp()
|
|
expect(editor.scrollTop()).toBe(0)
|
|
|
|
describe "when the editor is shorter than twice the vertical scroll margin", ->
|
|
it "sets the scrollTop based on a reduced scroll margin, which prevents a jerky tug-of-war between upper and lower scroll margins", ->
|
|
setEditorHeightInLines(editor, 5)
|
|
|
|
_.times 3, -> editor.moveCursorDown()
|
|
|
|
expect(editor.scrollTop()).toBe(editor.lineHeight)
|
|
|
|
editor.moveCursorUp()
|
|
expect(editor.renderedLines.css('top')).toBe "0px"
|
|
|
|
describe "when the last cursor exceeds the right or left scroll margins", ->
|
|
describe "when soft-wrap is disabled", ->
|
|
describe "when the editor is wider than twice the horizontal scroll margin", ->
|
|
it "sets the scrollView's scrollLeft so the cursor remains within the scroll margin", ->
|
|
setEditorWidthInChars(editor, 30)
|
|
|
|
# moving right
|
|
editor.setCursorScreenPosition([2, 24])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
editor.setCursorScreenPosition([2, 25])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe charWidth
|
|
|
|
editor.setCursorScreenPosition([2, 28])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe charWidth * 4
|
|
|
|
# moving left
|
|
editor.setCursorScreenPosition([2, 9])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe charWidth * 4
|
|
|
|
editor.setCursorScreenPosition([2, 8])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe charWidth * 3
|
|
|
|
editor.setCursorScreenPosition([2, 5])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
describe "when the editor is narrower than twice the horizontal scroll margin", ->
|
|
it "sets the scrollView's scrollLeft based on a reduced horizontal scroll margin, to prevent a jerky tug-of-war between right and left scroll margins", ->
|
|
editor.hScrollMargin = 6
|
|
setEditorWidthInChars(editor, 7)
|
|
|
|
editor.setCursorScreenPosition([2, 3])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe(0)
|
|
|
|
editor.setCursorScreenPosition([2, 4])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe(charWidth)
|
|
|
|
editor.setCursorScreenPosition([2, 3])
|
|
window.advanceClock()
|
|
expect(editor.scrollView.scrollLeft()).toBe(0)
|
|
|
|
describe "when soft-wrap is enabled", ->
|
|
beforeEach ->
|
|
editor.setSoftWrap(true)
|
|
|
|
it "does not scroll the buffer horizontally", ->
|
|
editor.width(charWidth * 30)
|
|
|
|
# moving right
|
|
editor.setCursorScreenPosition([2, 24])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
editor.setCursorScreenPosition([2, 25])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
editor.setCursorScreenPosition([2, 28])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
# moving left
|
|
editor.setCursorScreenPosition([2, 9])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
editor.setCursorScreenPosition([2, 8])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
editor.setCursorScreenPosition([2, 5])
|
|
expect(editor.scrollView.scrollLeft()).toBe 0
|
|
|
|
describe "text rendering", ->
|
|
describe "when all lines in the buffer are visible on screen", ->
|
|
beforeEach ->
|
|
editor.attachToDom()
|
|
expect(editor.height()).toBe buffer.getLineCount() * editor.lineHeight
|
|
|
|
it "creates a line element for each line in the buffer with the html-escaped text of the line", ->
|
|
expect(editor.renderedLines.find('.line').length).toEqual(buffer.getLineCount())
|
|
expect(buffer.lineForRow(2)).toContain('<')
|
|
expect(editor.renderedLines.find('.line:eq(2)').html()).toContain '<'
|
|
|
|
# renders empty lines with a non breaking space
|
|
expect(buffer.lineForRow(10)).toBe ''
|
|
expect(editor.renderedLines.find('.line:eq(10)').html()).toBe ' '
|
|
|
|
it "syntax highlights code based on the file type", ->
|
|
line1 = editor.renderedLines.find('.line:first')
|
|
expect(line1.find('span:eq(0)')).toMatchSelector '.keyword.definition'
|
|
expect(line1.find('span:eq(0)').text()).toBe 'var'
|
|
expect(line1.find('span:eq(1)')).toMatchSelector '.text'
|
|
expect(line1.find('span:eq(1)').text()).toBe ' '
|
|
expect(line1.find('span:eq(2)')).toMatchSelector '.identifier'
|
|
expect(line1.find('span:eq(2)').text()).toBe 'quicksort'
|
|
expect(line1.find('span:eq(4)')).toMatchSelector '.operator'
|
|
expect(line1.find('span:eq(4)').text()).toBe '='
|
|
|
|
line12 = editor.renderedLines.find('.line:eq(11)')
|
|
expect(line12.find('span:eq(1)')).toMatchSelector '.keyword'
|
|
|
|
describe "when lines are updated in the buffer", ->
|
|
it "syntax highlights the updated lines", ->
|
|
expect(editor.renderedLines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
|
buffer.insert([0, 4], "g")
|
|
expect(editor.renderedLines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
|
|
|
# verify that re-highlighting can occur below the changed line
|
|
buffer.insert([5,0], "/* */")
|
|
buffer.insert([1,0], "/*")
|
|
expect(editor.renderedLines.find('.line:eq(2) span:eq(0)')).toMatchSelector '.comment'
|
|
|
|
describe "when soft-wrap is enabled", ->
|
|
beforeEach ->
|
|
setEditorHeightInLines(editor, 20)
|
|
setEditorWidthInChars(editor, 50)
|
|
editor.setSoftWrap(true)
|
|
expect(editor.activeEditSession.softWrapColumn).toBe 50
|
|
|
|
it "wraps lines that are too long to fit within the editor's width, adjusting cursor positioning accordingly", ->
|
|
expect(editor.renderedLines.find('.line').length).toBe 16
|
|
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe " var pivot = items.shift(), current, left = [], "
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe "right = [];"
|
|
|
|
editor.setCursorBufferPosition([3, 51])
|
|
expect(editor.find('.cursor').offset()).toEqual(editor.renderedLines.find('.line:eq(4)').offset())
|
|
|
|
editor.setCursorBufferPosition([4, 0])
|
|
expect(editor.find('.cursor').offset()).toEqual(editor.renderedLines.find('.line:eq(5)').offset())
|
|
|
|
editor.getSelection().setBufferRange(new Range([6, 30], [6, 55]))
|
|
[region1, region2] = editor.getSelectionView().regions
|
|
expect(region1.offset().top).toBe(editor.renderedLines.find('.line:eq(7)').offset().top)
|
|
expect(region2.offset().top).toBe(editor.renderedLines.find('.line:eq(8)').offset().top)
|
|
|
|
it "handles changes to wrapped lines correctly", ->
|
|
buffer.insert([6, 28], '1234567')
|
|
expect(editor.renderedLines.find('.line:eq(7)').text()).toBe ' current < pivot ? left1234567.push(current) '
|
|
expect(editor.renderedLines.find('.line:eq(8)').text()).toBe ': right.push(current);'
|
|
expect(editor.renderedLines.find('.line:eq(9)').text()).toBe ' }'
|
|
|
|
it "changes the max line length and repositions the cursor when the window size changes", ->
|
|
editor.setCursorBufferPosition([3, 60])
|
|
setEditorWidthInChars(editor, 40)
|
|
$(window).trigger 'resize'
|
|
expect(editor.renderedLines.find('.line').length).toBe 19
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe "left = [], right = [];"
|
|
expect(editor.renderedLines.find('.line:eq(5)').text()).toBe " while(items.length > 0) {"
|
|
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
|
|
|
|
it "wraps the lines of any newly assigned buffers", ->
|
|
otherBuffer = new Buffer
|
|
otherBuffer.setText([1..100].join(''))
|
|
editor.setBuffer(otherBuffer)
|
|
expect(editor.renderedLines.find('.line').length).toBeGreaterThan(1)
|
|
|
|
it "unwraps lines and cancels window resize listener when softwrap is disabled", ->
|
|
editor.toggleSoftWrap()
|
|
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe ' var pivot = items.shift(), current, left = [], right = [];'
|
|
|
|
spyOn(editor, 'setSoftWrapColumn')
|
|
$(window).trigger 'resize'
|
|
expect(editor.setSoftWrapColumn).not.toHaveBeenCalled()
|
|
|
|
it "allows the cursor to move down to the last line", ->
|
|
_.times editor.getLastScreenRow(), -> editor.moveCursorDown()
|
|
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 0]
|
|
editor.moveCursorDown()
|
|
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 2]
|
|
|
|
it "allows the cursor to move up to a shorter soft wrapped line", ->
|
|
editor.setCursorScreenPosition([11, 15])
|
|
editor.moveCursorUp()
|
|
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
|
editor.moveCursorUp()
|
|
editor.moveCursorUp()
|
|
expect(editor.getCursorScreenPosition()).toEqual [8, 15]
|
|
|
|
it "it allows the cursor to wrap when moving horizontally past the beginning / end of a wrapped line", ->
|
|
editor.setCursorScreenPosition([11, 0])
|
|
editor.moveCursorLeft()
|
|
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
|
|
|
editor.moveCursorRight()
|
|
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
|
|
|
|
it "calls .setSoftWrapColumn() when the editor is attached because now its dimensions are available to calculate it", ->
|
|
otherEditor = new Editor()
|
|
spyOn(otherEditor, 'setSoftWrapColumn')
|
|
|
|
otherEditor.setSoftWrap(true)
|
|
expect(otherEditor.setSoftWrapColumn).not.toHaveBeenCalled()
|
|
|
|
otherEditor.simulateDomAttachment()
|
|
expect(otherEditor.setSoftWrapColumn).toHaveBeenCalled()
|
|
otherEditor.remove()
|
|
|
|
describe "when some lines at the end of the buffer are not visible on screen", ->
|
|
beforeEach ->
|
|
editor.attachToDom(heightInLines: 5.5)
|
|
|
|
it "only renders the visible lines plus the overdrawn lines, setting the padding-bottom of the lines element to account for the missing lines", ->
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expectedPaddingBottom = (buffer.getLineCount() - 8) * editor.lineHeight
|
|
expect(editor.renderedLines.css('padding-bottom')).toBe "#{expectedPaddingBottom}px"
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
|
|
|
|
it "renders additional lines when the editor is resized", ->
|
|
setEditorHeightInLines(editor, 10)
|
|
$(window).trigger 'resize'
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 12
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(11)
|
|
|
|
it "renders correctly when scrolling after text is added to the buffer", ->
|
|
editor.insertText("1\n")
|
|
_.times 4, -> editor.moveCursorDown()
|
|
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe editor.buffer.lineForRow(2)
|
|
expect(editor.renderedLines.find('.line:eq(7)').text()).toBe editor.buffer.lineForRow(7)
|
|
|
|
it "renders correctly when scrolling after text is removed from buffer", ->
|
|
editor.buffer.delete([[0,0],[1,0]])
|
|
expect(editor.renderedLines.find('.line:eq(0)').text()).toBe editor.buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:eq(5)').text()).toBe editor.buffer.lineForRow(5)
|
|
|
|
editor.scrollTop(3 * editor.lineHeight)
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe editor.buffer.lineForRow(1)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe editor.buffer.lineForRow(10)
|
|
|
|
describe "when creating and destroying folds that are longer than the visible lines", ->
|
|
describe "when the cursor precedes the fold when it is destroyed", ->
|
|
it "renders lines and line numbers correctly", ->
|
|
scrollHeightBeforeFold = editor.scrollView.prop('scrollHeight')
|
|
fold = editor.createFold(1, 9)
|
|
fold.destroy()
|
|
expect(editor.scrollView.prop('scrollHeight')).toBe scrollHeightBeforeFold
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
|
|
|
|
expect(editor.gutter.find('.line-number').length).toBe 8
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '8'
|
|
|
|
editor.scrollTop(4 * editor.lineHeight)
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(11)
|
|
|
|
describe "when the cursor follows the fold when it is destroyed", ->
|
|
it "renders lines and line numbers correctly", ->
|
|
fold = editor.createFold(1, 9)
|
|
editor.setCursorBufferPosition([10, 0])
|
|
fold.destroy()
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(12)
|
|
|
|
expect(editor.gutter.find('.line-number').length).toBe 8
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '13'
|
|
|
|
editor.scrollTop(4 * editor.lineHeight)
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(11)
|
|
|
|
describe "when scrolling vertically", ->
|
|
describe "when scrolling less than the editor's height", ->
|
|
it "draws new lines and removes old lines when the last visible line will exceed the last rendered line", ->
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
|
|
editor.scrollTop(editor.lineHeight * 1.5)
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
|
|
|
|
editor.scrollTop(editor.lineHeight * 3.5) # first visible row will be 3, last will be 8
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1)
|
|
expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe '2'
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '11'
|
|
|
|
# here we don't scroll far enough to trigger additional rendering
|
|
editor.scrollTop(editor.lineHeight * 5.5) # first visible row will be 5, last will be 10
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1)
|
|
expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe '2'
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '11'
|
|
|
|
editor.scrollTop(editor.lineHeight * 7.5) # first visible row is 7, last will be 12
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(5)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(12)
|
|
|
|
editor.scrollTop(editor.lineHeight * 3.5) # first visible row will be 3, last will be 8
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(1)
|
|
expect(editor.renderedLines.find('.line:last').html()).toBe ' ' # line 10 is blank
|
|
|
|
editor.scrollTop(0)
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
|
|
|
|
describe "when scrolling more than the editors height", ->
|
|
it "removes lines that are offscreen and not in range of the overdraw and builds lines that become visible", ->
|
|
editor.scrollTop(editor.scrollView.prop('scrollHeight') - editor.scrollView.height())
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(5)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(12)
|
|
|
|
editor.verticalScrollbar.scrollBottom(0)
|
|
editor.verticalScrollbar.trigger 'scroll'
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
|
|
|
|
it "adjusts the vertical padding of the lines element to account for non-rendered lines", ->
|
|
editor.scrollTop(editor.lineHeight * 3)
|
|
firstVisibleBufferRow = 3
|
|
expectedPaddingTop = (firstVisibleBufferRow - editor.lineOverdraw) * editor.lineHeight
|
|
expect(editor.renderedLines.css('padding-top')).toBe "#{expectedPaddingTop}px"
|
|
|
|
lastVisibleBufferRow = Math.ceil(3 + 5.5) # scroll top in lines + height in lines
|
|
lastOverdrawnRow = lastVisibleBufferRow + editor.lineOverdraw
|
|
expectedPaddingBottom = ((buffer.getLineCount() - lastOverdrawnRow) * editor.lineHeight)
|
|
expect(editor.renderedLines.css('padding-bottom')).toBe "#{expectedPaddingBottom}px"
|
|
|
|
editor.scrollToBottom()
|
|
# scrolled to bottom, first visible row is 5 and first rendered row is 3
|
|
firstVisibleBufferRow = Math.floor(buffer.getLineCount() - 5.5)
|
|
firstOverdrawnBufferRow = firstVisibleBufferRow - editor.lineOverdraw
|
|
expectedPaddingTop = firstOverdrawnBufferRow * editor.lineHeight
|
|
expect(editor.renderedLines.css('padding-top')).toBe "#{expectedPaddingTop}px"
|
|
expect(editor.renderedLines.css('padding-bottom')).toBe "0px"
|
|
|
|
describe "when lines are added", ->
|
|
beforeEach ->
|
|
editor.attachToDom(heightInLines: 5)
|
|
spyOn(editor, "scrollTo")
|
|
|
|
describe "when the change the precedes the first rendered row", ->
|
|
it "inserts and removes rendered lines to account for upstream change", ->
|
|
editor.scrollToBottom()
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
|
|
|
buffer.change([[1,0], [3,0]], "1\n2\n3\n")
|
|
expect(editor.renderedLines.find(".line").length).toBe 8
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(13)
|
|
|
|
describe "when the change straddles the first rendered row", ->
|
|
it "doesn't render rows that were not previously rendered", ->
|
|
editor.scrollToBottom()
|
|
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
|
|
|
buffer.change([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n9\n")
|
|
expect(editor.renderedLines.find(".line").length).toBe 9
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(14)
|
|
|
|
describe "when the change straddles the last rendered row", ->
|
|
it "doesn't render rows that were not previously rendered", ->
|
|
buffer.change([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n")
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
|
|
|
describe "when the change the follows the last rendered row", ->
|
|
it "does not change the rendered lines", ->
|
|
buffer.change([[12,0], [12,0]], "12\n13\n14\n")
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
|
|
|
it "increases the width of the rendered lines element if the max line length changes", ->
|
|
widthBefore = editor.renderedLines.width()
|
|
buffer.change([[12,0], [12,0]], [1..50].join(''))
|
|
expect(editor.renderedLines.width()).toBeGreaterThan widthBefore
|
|
|
|
describe "when lines are removed", ->
|
|
beforeEach ->
|
|
editor.attachToDom(heightInLines: 5)
|
|
spyOn(editor, "scrollTo")
|
|
|
|
describe "when the change the precedes the first rendered row", ->
|
|
it "removes rendered lines to account for upstream change", ->
|
|
editor.scrollToBottom()
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
|
|
|
buffer.change([[1,0], [2,0]], "")
|
|
expect(editor.renderedLines.find(".line").length).toBe 6
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(11)
|
|
|
|
describe "when the change straddles the first rendered row", ->
|
|
it "renders the correct rows", ->
|
|
editor.scrollToBottom()
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
|
|
|
buffer.change([[7,0], [11,0]], "1\n2\n")
|
|
expect(editor.renderedLines.find(".line").length).toBe 5
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(10)
|
|
|
|
describe "when the change straddles the last rendered row", ->
|
|
it "renders the correct rows", ->
|
|
buffer.change([[2,0], [7,0]], "")
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
|
|
|
describe "when the change the follows the last rendered row", ->
|
|
it "does not change the rendered lines", ->
|
|
buffer.change([[10,0], [12,0]], "")
|
|
expect(editor.renderedLines.find(".line").length).toBe 7
|
|
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
|
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
|
|
|
it "decreases the width of the rendered screen lines if the max line length changes", ->
|
|
widthBefore = editor.renderedLines.width()
|
|
buffer.delete([[6, 0], [6, Infinity]])
|
|
expect(editor.renderedLines.width()).toBeLessThan widthBefore
|
|
|
|
describe "when folding leaves less then a screen worth of text (regression)", ->
|
|
it "renders lines properly", ->
|
|
editor.lineOverdraw = 1
|
|
editor.attachToDom(heightInLines: 5)
|
|
editor.activeEditSession.toggleFoldAtBufferRow(4)
|
|
editor.activeEditSession.toggleFoldAtBufferRow(0)
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 1
|
|
expect(editor.renderedLines.find('.line').text()).toBe buffer.lineForRow(0)
|
|
|
|
describe "when autoscrolling at the end of the document", ->
|
|
it "renders lines properly", ->
|
|
editor.setBuffer(new Buffer(require.resolve 'fixtures/two-hundred.txt'))
|
|
editor.attachToDom(heightInLines: 5.5)
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
|
|
editor.moveCursorToBottom()
|
|
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
|
|
describe ".spliceLineElements(startRow, rowCount, lineElements)", ->
|
|
elements = null
|
|
|
|
beforeEach ->
|
|
editor.attachToDom()
|
|
elements = $$ ->
|
|
@div "A", class: 'line'
|
|
@div "B", class: 'line'
|
|
|
|
describe "when the start row is 0", ->
|
|
describe "when the row count is 0", ->
|
|
it "inserts the given elements before the first row", ->
|
|
editor.spliceLineElements 0, 0, elements
|
|
|
|
expect(editor.renderedLines.find('.line:eq(0)').text()).toBe 'A'
|
|
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe 'B'
|
|
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'var quicksort = function () {'
|
|
|
|
describe "when the row count is > 0", ->
|
|
it "replaces the initial rows with the given elements", ->
|
|
editor.spliceLineElements 0, 2, elements
|
|
|
|
expect(editor.renderedLines.find('.line:eq(0)').text()).toBe 'A'
|
|
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe 'B'
|
|
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe ' if (items.length <= 1) return items;'
|
|
|
|
describe "when the start row is less than the last row", ->
|
|
describe "when the row count is 0", ->
|
|
it "inserts the elements at the specified location", ->
|
|
editor.spliceLineElements 2, 0, elements
|
|
|
|
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'A'
|
|
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe 'B'
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe ' if (items.length <= 1) return items;'
|
|
|
|
describe "when the row count is > 0", ->
|
|
it "replaces the elements at the specified location", ->
|
|
editor.spliceLineElements 2, 2, elements
|
|
|
|
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'A'
|
|
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe 'B'
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe ' while(items.length > 0) {'
|
|
|
|
describe "when the start row is the last row", ->
|
|
it "appends the elements to the end of the lines", ->
|
|
editor.spliceLineElements 13, 0, elements
|
|
|
|
expect(editor.renderedLines.find('.line:eq(12)').text()).toBe '};'
|
|
expect(editor.renderedLines.find('.line:eq(13)').text()).toBe 'A'
|
|
expect(editor.renderedLines.find('.line:eq(14)').text()).toBe 'B'
|
|
expect(editor.renderedLines.find('.line:eq(15)')).not.toExist()
|
|
|
|
describe "gutter rendering", ->
|
|
beforeEach ->
|
|
editor.attachToDom(heightInLines: 5.5)
|
|
|
|
it "creates a line number element for each visible line, plus overdraw", ->
|
|
expect(editor.gutter.find('.line-number').length).toBe 8
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe "1"
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe "8"
|
|
|
|
# here we don't scroll far enough to trigger additional rendering
|
|
editor.scrollTop(editor.lineHeight * 1.5)
|
|
expect(editor.renderedLines.find('.line').length).toBe 8
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe "1"
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe "8"
|
|
|
|
editor.scrollTop(editor.lineHeight * 3.5)
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe "2"
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe "11"
|
|
|
|
describe "width", ->
|
|
it "sets the width based on last line number", ->
|
|
expect(editor.gutter.lineNumbers.outerWidth()).toBe editor.charWidth * 2
|
|
|
|
it "updates the width when total number of lines gains a digit", ->
|
|
oneHundredLines = [0..100].join("\n")
|
|
editor.insertText(oneHundredLines)
|
|
expect(editor.gutter.lineNumbers.outerWidth()).toBe editor.charWidth * 3
|
|
|
|
describe "when lines are inserted", ->
|
|
it "re-renders the correct line number range in the gutter", ->
|
|
spyOn(editor, 'scrollTo')
|
|
editor.scrollTop(3 * editor.lineHeight)
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe '2'
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '11'
|
|
|
|
buffer.insert([6, 0], '\n')
|
|
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe '2'
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe '11'
|
|
|
|
describe "when the insertion of lines causes the editor to scroll", ->
|
|
it "renders line numbers correctly", ->
|
|
oneHundredLines = [0..100].join("\n")
|
|
editor.insertText(oneHundredLines)
|
|
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 6 + editor.lineOverdraw * 2
|
|
|
|
describe "when wrapping is on", ->
|
|
it "renders a • instead of line number for wrapped portions of lines", ->
|
|
editor.setSoftWrapColumn(50)
|
|
expect(editor.gutter.find('.line-number').length).toEqual(8)
|
|
expect(editor.gutter.find('.line-number:eq(3)').text()).toBe '4'
|
|
expect(editor.gutter.find('.line-number:eq(4)').text()).toBe '•'
|
|
expect(editor.gutter.find('.line-number:eq(5)').text()).toBe '5'
|
|
|
|
describe "when there are folds", ->
|
|
it "skips line numbers covered by the fold and updates them when the fold changes", ->
|
|
editor.createFold(3, 5)
|
|
expect(editor.gutter.find('.line-number:eq(3)').text()).toBe '4'
|
|
expect(editor.gutter.find('.line-number:eq(4)').text()).toBe '7'
|
|
|
|
buffer.insert([4,0], "\n\n")
|
|
expect(editor.gutter.find('.line-number:eq(3)').text()).toBe '4'
|
|
expect(editor.gutter.find('.line-number:eq(4)').text()).toBe '9'
|
|
|
|
buffer.delete([[3,0], [6,0]])
|
|
expect(editor.gutter.find('.line-number:eq(3)').text()).toBe '4'
|
|
expect(editor.gutter.find('.line-number:eq(4)').text()).toBe '6'
|
|
|
|
it "redraws gutter numbers when lines are unfolded", ->
|
|
setEditorHeightInLines(editor, 20)
|
|
fold = editor.createFold(2, 12)
|
|
expect(editor.gutter.find('.line-number').length).toBe 3
|
|
|
|
fold.destroy()
|
|
expect(editor.gutter.find('.line-number').length).toBe 13
|
|
|
|
describe "when the scrollView is scrolled to the right", ->
|
|
it "adds a drop shadow to the gutter", ->
|
|
editor.attachToDom()
|
|
editor.width(100)
|
|
|
|
expect(editor.gutter).not.toHaveClass('drop-shadow')
|
|
|
|
editor.scrollView.scrollLeft(10)
|
|
editor.scrollView.trigger('scroll')
|
|
|
|
expect(editor.gutter).toHaveClass('drop-shadow')
|
|
|
|
editor.scrollView.scrollLeft(0)
|
|
editor.scrollView.trigger('scroll')
|
|
|
|
expect(editor.gutter).not.toHaveClass('drop-shadow')
|
|
|
|
describe "when the editor is scrolled vertically", ->
|
|
it "adjusts the padding-top to account for non-rendered line numbers", ->
|
|
editor.scrollTop(editor.lineHeight * 3.5)
|
|
expect(editor.gutter.lineNumbers.css('padding-top')).toBe "#{editor.lineHeight * 1}px"
|
|
expect(editor.gutter.lineNumbers.css('padding-bottom')).toBe "#{editor.lineHeight * 2}px"
|
|
expect(editor.renderedLines.find('.line').length).toBe 10
|
|
expect(editor.gutter.find('.line-number:first').text()).toBe "2"
|
|
expect(editor.gutter.find('.line-number:last').text()).toBe "11"
|
|
|
|
describe "folding", ->
|
|
beforeEach ->
|
|
editor.setBuffer(new Buffer(require.resolve('fixtures/two-hundred.txt')))
|
|
editor.attachToDom()
|
|
|
|
describe "when a fold-selection event is triggered", ->
|
|
it "folds the lines covered by the selection into a single line with a fold class", ->
|
|
editor.getSelection().setBufferRange(new Range([4, 29], [7, 4]))
|
|
editor.trigger 'fold-selection'
|
|
|
|
expect(editor.renderedLines.find('.line:eq(4)')).toHaveClass('fold')
|
|
expect(editor.renderedLines.find('.line:eq(5)').text()).toBe '8'
|
|
|
|
expect(editor.getSelection().isEmpty()).toBeTruthy()
|
|
expect(editor.getCursorScreenPosition()).toEqual [5, 0]
|
|
|
|
describe "when a fold placeholder line is clicked", ->
|
|
it "removes the associated fold and places the cursor at its beginning", ->
|
|
editor.getSelection().setBufferRange(new Range([3, 0], [9, 0]))
|
|
editor.trigger 'fold-selection'
|
|
|
|
editor.find('.fold.line').mousedown()
|
|
|
|
expect(editor.find('.fold')).not.toExist()
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toMatch /4-+/
|
|
expect(editor.renderedLines.find('.line:eq(5)').text()).toMatch /5/
|
|
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
|
|
describe "when the unfold event is triggered when the cursor is on a fold placeholder line", ->
|
|
it "removes the associated fold and places the cursor at its beginning", ->
|
|
editor.getSelection().setBufferRange(new Range([3, 0], [9, 0]))
|
|
editor.trigger 'fold-selection'
|
|
|
|
editor.setCursorBufferPosition([3,0])
|
|
editor.trigger 'unfold'
|
|
|
|
expect(editor.find('.fold')).not.toExist()
|
|
expect(editor.renderedLines.find('.line:eq(4)').text()).toMatch /4-+/
|
|
expect(editor.renderedLines.find('.line:eq(5)').text()).toMatch /5/
|
|
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
|
|
describe "when a selection starts/stops intersecting a fold", ->
|
|
it "adds/removes the 'selected' class to the fold's line element and hides the cursor if it is on the fold line", ->
|
|
editor.createFold(2, 4)
|
|
|
|
editor.setSelectedBufferRange([[1, 0], [2, 0]], reverse: true)
|
|
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|
|
|
|
editor.setSelectedBufferRange([[1, 0], [1, 1]])
|
|
expect(editor.lineElementForScreenRow(2)).not.toMatchSelector('.fold.selected')
|
|
|
|
editor.setSelectedBufferRange([[1, 0], [5, 0]])
|
|
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|
|
|
|
editor.setCursorScreenPosition([3,0])
|
|
expect(editor.lineElementForScreenRow(2)).not.toMatchSelector('.fold.selected')
|
|
|
|
editor.setCursorScreenPosition([2,0])
|
|
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|
|
expect(editor.find('.cursor').css('display')).toBe 'none'
|
|
|
|
editor.setCursorScreenPosition([3,0])
|
|
expect(editor.find('.cursor').css('display')).toBe 'block'
|
|
|
|
describe "when a selected fold is scrolled into view (and the fold line was not previously rendered)", ->
|
|
it "renders the fold's line element with the 'selected' class", ->
|
|
setEditorHeightInLines(editor, 5)
|
|
editor.renderLines() # re-render lines so certain lines are not rendered
|
|
|
|
editor.createFold(2, 4)
|
|
editor.setSelectedBufferRange([[1, 0], [5, 0]])
|
|
expect(editor.renderedLines.find('.fold.selected')).toExist()
|
|
|
|
editor.scrollToBottom()
|
|
expect(editor.renderedLines.find('.fold.selected')).not.toExist()
|
|
|
|
editor.scrollTop(0)
|
|
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|