diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 05ac87c0c..f8117af09 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -635,16 +635,28 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setExplicitHeight(500) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500 - it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> - presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) - expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + describe "scrollPastEnd", -> + it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> + presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) + expectStateUpdate presenter, -> presenter.setScrollTop(300) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight - expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) - expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) + expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) - expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + + it "doesn't add the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true but the presenter is created with scrollPastEnd as false", -> + presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10, scrollPastEnd: false) + expectStateUpdate presenter, -> presenter.setScrollTop(300) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + + expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + + expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) + expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight describe ".scrollTop", -> it "tracks the value of ::scrollTop", -> diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 1c0d3ad02..9d2a2a58c 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -5828,3 +5828,30 @@ describe "TextEditor", -> screenRange: marker1.getRange(), rangeIsReversed: false } + + describe "when the editor is constructed with the showInvisibles option set to false", -> + beforeEach -> + atom.workspace.destroyActivePane() + waitsForPromise -> + atom.workspace.open('sample.js', showInvisibles: false).then (o) -> editor = o + + it "ignores invisibles even if editor.showInvisibles is true", -> + atom.config.set('editor.showInvisibles', true) + invisibles = editor.tokenizedLineForScreenRow(0).invisibles + expect(invisibles).toBe(null) + + describe "when the editor is constructed with the grammar option set", -> + beforeEach -> + atom.workspace.destroyActivePane() + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + waitsForPromise -> + atom.workspace.open('sample.js', grammar: atom.grammars.grammarForScopeName('source.coffee')).then (o) -> editor = o + + it "sets the grammar", -> + expect(editor.getGrammar().name).toBe 'CoffeeScript' + + describe "::getElement", -> + it "returns an element", -> + expect(editor.getElement() instanceof HTMLElement).toBe(true) diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee index 16672b25d..68a482b48 100644 --- a/spec/view-registry-spec.coffee +++ b/spec/view-registry-spec.coffee @@ -23,6 +23,15 @@ describe "ViewRegistry", -> component = new TestComponent expect(registry.getView(component)).toBe component.element + describe "when passed an object with a getElement function", -> + it "returns the return value of getElement if it's an instance of HTMLElement", -> + class TestComponent + getElement: -> + @myElement ?= document.createElement('div') + + component = new TestComponent + expect(registry.getView(component)).toBe component.myElement + describe "when passed a model object", -> describe "when a view provider is registered matching the object's constructor", -> it "constructs a view element and assigns the model on it", -> diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 661201125..3d43bdfd8 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -265,8 +265,6 @@ class AtomEnvironment extends Model new PaneAxisElement().initialize(model, env) @views.addViewProvider Pane, (model, env) -> new PaneElement().initialize(model, env) - @views.addViewProvider TextEditor, (model, env) -> - new TextEditorElement().initialize(model, env) @views.addViewProvider(Gutter, createGutterView) registerDefaultOpeners: -> diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index bdd0befcd..9b091100d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -43,7 +43,7 @@ class TextEditorComponent @assert domNode?, "TextEditorComponent::domNode was set to null." @domNodeValue = domNode - constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) -> + constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars, scrollPastEnd}) -> @tileSize = tileSize if tileSize? @disposables = new CompositeDisposable @@ -61,6 +61,7 @@ class TextEditorComponent stoppedScrollingDelay: 200 config: @config lineTopIndex: lineTopIndex + scrollPastEnd: scrollPastEnd @presenter.onDidUpdateState(@requestUpdate) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 380417163..df13f2a15 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -17,6 +17,7 @@ class TextEditorElement extends HTMLElement focusOnAttach: false hasTiledRendering: true logicalDisplayBuffer: true + scrollPastEnd: true createdCallback: -> # Use globals when the following instance variables aren't set. @@ -38,6 +39,9 @@ class TextEditorElement extends HTMLElement @setAttribute('tabindex', -1) initializeContent: (attributes) -> + unless @autoHeight + @style.height = "100%" + if @config.get('editor.useShadowDOM') @useShadowDOM = true @@ -86,7 +90,7 @@ class TextEditorElement extends HTMLElement @subscriptions.add @component.onDidChangeScrollLeft => @emitter.emit("did-change-scroll-left", arguments...) - initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) -> + initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}, @autoHeight = true, @scrollPastEnd = true) -> throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @views? throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @config? throw new Error("Must pass a themes parameter when initializing TextEditorElements") unless @themes? @@ -143,6 +147,7 @@ class TextEditorElement extends HTMLElement workspace: @workspace assert: @assert grammars: @grammars + scrollPastEnd: @scrollPastEnd ) @rootElement.appendChild(@component.getDomNode()) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a3504caa8..ef1b403c3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -13,7 +13,7 @@ class TextEditorPresenter minimumReflowInterval: 200 constructor: (params) -> - {@model, @config, @lineTopIndex} = params + {@model, @config, @lineTopIndex, scrollPastEnd} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params {@contentFrameWidth} = params @@ -42,6 +42,8 @@ class TextEditorPresenter @startReflowing() if @continuousReflow @updating = false + @scrollPastEndOverride = scrollPastEnd ? true + setLinesYardstick: (@linesYardstick) -> getLinesYardstick: -> @linesYardstick @@ -661,7 +663,7 @@ class TextEditorPresenter return unless @contentHeight? and @clientHeight? contentHeight = @contentHeight - if @scrollPastEnd + if @scrollPastEnd and @scrollPastEndOverride extraScrollHeight = @clientHeight - (@lineHeight * 3) contentHeight += extraScrollHeight if extraScrollHeight > 0 scrollHeight = Math.max(contentHeight, @height) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 5e384fde1..d5937d307 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -11,6 +11,7 @@ Selection = require './selection' TextMateScopeSelector = require('first-mate').ScopeSelector {Directory} = require "pathwatcher" GutterContainer = require './gutter-container' +TextEditorElement = require './text-editor-element' # Essential: This class represents all essential editing state for a single # {TextBuffer}, including cursor and selection positions, folds, and soft wraps. @@ -61,6 +62,10 @@ class TextEditor extends Model suppressSelectionMerging: false selectionFlashDuration: 500 gutterContainer: null + editorElement: null + + Object.defineProperty @prototype, "element", + get: -> @getElement() @deserialize: (state, atomEnvironment) -> try @@ -95,7 +100,7 @@ class TextEditor extends Model softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry, - @project, @assert, @applicationDelegate + @project, @assert, @applicationDelegate, grammar, showInvisibles, @autoHeight, @scrollPastEnd } = params throw new Error("Must pass a config parameter when constructing TextEditors") unless @config? @@ -114,11 +119,15 @@ class TextEditor extends Model @cursors = [] @cursorsByMarkerId = new Map @selections = [] + @autoHeight ?= true + @scrollPastEnd ?= true @hasTerminatedPendingState = false + showInvisibles ?= true + buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({ - buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode, + buffer, tabLength, softWrapped, ignoreInvisibles: @mini or not showInvisibles, largeFileMode, @config, @assert, @grammarRegistry, @packageManager }) @buffer = @displayBuffer.buffer @@ -147,6 +156,9 @@ class TextEditor extends Model priority: 0 visible: lineNumberGutterVisible + if grammar? + @setGrammar(grammar) + serialize: -> deserializer: 'TextEditor' id: @id @@ -3142,6 +3154,10 @@ class TextEditor extends Model Section: TextEditor Rendering ### + # Get the Element for the editor. + getElement: -> + @editorElement ?= new TextEditorElement().initialize(this, atom, @autoHeight, @scrollPastEnd) + # Essential: Retrieves the greyed out placeholder of a mini editor. # # Returns a {String}. @@ -3216,7 +3232,7 @@ class TextEditor extends Model setFirstVisibleScreenRow: (screenRow, fromView) -> unless fromView maxScreenRow = @getScreenLineCount() - 1 - unless @config.get('editor.scrollPastEnd') + unless @config.get('editor.scrollPastEnd') and @scrollPastEnd height = @displayBuffer.getHeight() lineHeightInPixels = @displayBuffer.getLineHeightInPixels() if height? and lineHeightInPixels? diff --git a/src/view-registry.coffee b/src/view-registry.coffee index ef7151353..5fbfba729 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -171,6 +171,11 @@ class ViewRegistry if object instanceof HTMLElement return object + if typeof object?.getElement is 'function' + element = object.getElement() + if element instanceof HTMLElement + return element + if object?.element instanceof HTMLElement return object.element diff --git a/src/workspace.coffee b/src/workspace.coffee index c925a495a..873d90370 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -43,6 +43,12 @@ class Workspace extends Model @defaultDirectorySearcher = new DefaultDirectorySearcher() @consumeServices(@packageManager) + # One cannot simply .bind here since it could be used as a component with + # Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always + # the newly created object. + realThis = this + @buildTextEditor = -> Workspace.prototype.buildTextEditor.apply(realThis, arguments) + @panelContainers = top: new PanelContainer({location: 'top'}) left: new PanelContainer({location: 'left'})