From 8e69b0c4a0adf16be0bf4fd32ab7b3416b0de19e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 25 Jul 2014 13:59:23 -0700 Subject: [PATCH 1/4] Base font styling on the computed style of the editor element Previously, font styling was always explicitly assigned via the config. This commit is the first step in basing the font styling of the editor on the styles assigned via CSS. This will allow the editor's font-family, font-size, and line-height to be assigned via CSS just like they are for any other element, which will make it easier to style mini editors. We still need to switch the font settings to adjust a global stylesheet rather than updating inline styles on each editor individually. --- src/editor-component.coffee | 79 ++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index ffa585d26..d694fada1 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -40,7 +40,7 @@ EditorComponent = React.createClass scrollSensitivity: 0.4 heightAndWidthMeasurementRequested: false measureLineHeightAndDefaultCharWidthWhenShown: false - remeasureCharacterWidthsIfVisibleAfterNextUpdate: false + remeasureCharacterWidthsWhenShown: false inputEnabled: true scopedCharacterWidthsChangeCount: null domPollingInterval: 100 @@ -48,13 +48,12 @@ EditorComponent = React.createClass domPollingPaused: false render: -> - {focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state + {focused, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state {editor, mini, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props maxLineNumberDigits = editor.getLineCount().toString().length invisibles = if showInvisibles and not mini then @state.invisibles else {} hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty() - style = {fontSize, fontFamily} - style.lineHeight = lineHeight unless mini + style = {} if @performedInitialMeasurement renderedRowRange = @getRenderedRowRange() @@ -212,15 +211,15 @@ EditorComponent = React.createClass if @performedInitialMeasurement @measureScrollbars() if @measuringScrollbars - @measureLineHeightAndDefaultCharWidthIfNeeded(prevState) - @remeasureCharacterWidthsIfNeeded(prevState) performInitialMeasurement: -> @updatesPaused = true - @measureLineHeightAndDefaultCharWidth() @measureHeightAndWidth() + @sampleFontStyling() @sampleBackgroundColors() @measureScrollbars() + @measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown + @remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown @props.editor.setVisible(true) @updatesPaused = false @performedInitialMeasurement = true @@ -250,6 +249,9 @@ EditorComponent = React.createClass @updateRequestedWhilePaused = false @forceUpdate() + getTopmostDOMNode: -> + @props.parentView.element + getRenderedRowRange: -> {editor, lineOverdrawMargin} = @props [visibleStartRow, visibleEndRow] = editor.getVisibleRowRange() @@ -669,9 +671,9 @@ EditorComponent = React.createClass onStylesheetsChanged: (stylesheet) -> @refreshScrollbars() if @containsScrollbarSelector(stylesheet) + @sampleFontStyling() @sampleBackgroundColors() - @remeasureCharacterWidthsIfVisibleAfterNextUpdate = true - @requestUpdate() if @visible + @remeasureCharacterWidths() onScreenLinesChanged: (change) -> {editor} = @props @@ -769,6 +771,7 @@ EditorComponent = React.createClass if @visible = @isVisible() if wasVisible @measureHeightAndWidth() + @sampleFontStyling() @sampleBackgroundColors() else @performInitialMeasurement() @@ -811,6 +814,19 @@ EditorComponent = React.createClass clientWidth -= paddingLeft editor.setWidth(clientWidth) if clientWidth > 0 + sampleFontStyling: -> + oldFontSize = @fontSize + oldFontFamily = @fontFamily + oldLineHeight = @lineHeight + + {@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode()) + + if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight + @measureLineHeightAndDefaultCharWidth() + + if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement + @remeasureCharacterWidths() + sampleBackgroundColors: (suppressUpdate) -> {parentView} = @props {showLineNumbers} = @state @@ -826,31 +842,19 @@ EditorComponent = React.createClass @gutterBackgroundColor = gutterBackgroundColor @requestUpdate() unless suppressUpdate - measureLineHeightAndDefaultCharWidthIfNeeded: (prevState) -> - if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') - if @visible - @measureLineHeightAndDefaultCharWidth() - else - @measureLineHeightAndDefaultCharWidthWhenShown = true - else if @measureLineHeightAndDefaultCharWidthWhenShown and @visible - @measureLineHeightAndDefaultCharWidthWhenShown = false - @measureLineHeightAndDefaultCharWidth() - measureLineHeightAndDefaultCharWidth: -> - @refs.lines.measureLineHeightAndDefaultCharWidth() - - remeasureCharacterWidthsIfNeeded: (prevState) -> - if not isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') - if @visible - @remeasureCharacterWidths() - else - @remeasureCharacterWidthsIfVisibleAfterNextUpdate = true - else if @remeasureCharacterWidthsIfVisibleAfterNextUpdate and @visible - @remeasureCharacterWidthsIfVisibleAfterNextUpdate = false - @remeasureCharacterWidths() + if @visible + @measureLineHeightAndDefaultCharWidthWhenShown = false + @refs.lines.measureLineHeightAndDefaultCharWidth() + else + @measureLineHeightAndDefaultCharWidthWhenShown = true remeasureCharacterWidths: -> - @refs.lines.remeasureCharacterWidths() + if @visible + @remeasureCharacterWidthsWhenShown = false + @refs.lines.remeasureCharacterWidths() + else + @remeasureCharacterWidthsWhenShown = true measureScrollbars: -> return unless @visible @@ -911,19 +915,22 @@ EditorComponent = React.createClass null getFontSize: -> - @state.fontSize + parseInt(getComputedStyle(@getTopmostDOMNode()).fontSize) setFontSize: (fontSize) -> - @setState({fontSize}) + @getTopmostDOMNode().style.fontSize = fontSize + 'px' + @sampleFontStyling() getFontFamily: -> - @state.fontFamily + getComputedStyle(@getTopmostDOMNode()).fontFamily setFontFamily: (fontFamily) -> - @setState({fontFamily}) + @getTopmostDOMNode().style.fontFamily = fontFamily + @sampleFontStyling() setLineHeight: (lineHeight) -> - @setState({lineHeight}) + @getTopmostDOMNode().style.lineHeight = lineHeight + @sampleFontStyling() setShowIndentGuide: (showIndentGuide) -> @setState({showIndentGuide}) From 2b27c0b440aae03fb84cf0c395d7870c9eda2b01 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 25 Jul 2014 14:25:00 -0700 Subject: [PATCH 2/4] Only handle stylesheet changes after initial measurement --- src/editor-component.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index d694fada1..7eea4a516 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -670,6 +670,8 @@ EditorComponent = React.createClass editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]]) onStylesheetsChanged: (stylesheet) -> + return unless @performedInitialMeasurement + @refreshScrollbars() if @containsScrollbarSelector(stylesheet) @sampleFontStyling() @sampleBackgroundColors() From eebbb99fc886f7861e5396342d30c42edfb7aedb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 28 Jul 2014 11:23:36 -0600 Subject: [PATCH 3/4] Handle editor font config options with a global stylesheet Previously, each editor observed font-related config values on its own and applied inline styles to honor them. This made it difficult to style the editor like a normal element with CSS. Moving this to a global stylesheet that targets editors via the .editor selector means that the font size setting can be overridden in specific contexts, such as when using mini editors. --- spec/editor-component-spec.coffee | 4 ++-- spec/spec-helper.coffee | 2 ++ spec/workspace-view-spec.coffee | 22 +++++++++++++++++++++- src/editor-component.coffee | 3 --- src/workspace-view.coffee | 23 +++++++++++++++++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 2e60f3aec..0cffc74da 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -40,6 +40,7 @@ describe "EditorComponent", -> {component} = wrapperView component.performSyncUpdates = false + component.setFontFamily('monospace') component.setLineHeight(1.3) component.setFontSize(20) @@ -1975,7 +1976,7 @@ describe "EditorComponent", -> afterEach -> atom.themes.removeStylesheet('test') - it "does not re-measure character widths until the editor is shown again", -> + fit "does not re-measure character widths until the editor is shown again", -> atom.config.set('editor.fontFamily', 'sans-serif') wrapperView.hide() @@ -1984,7 +1985,6 @@ describe "EditorComponent", -> font-weight: bold; } """ - nextAnimationFrame() wrapperView.show() editor.setCursorBufferPosition([0, Infinity]) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 407cbb8a9..80207c952 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -130,6 +130,8 @@ afterEach -> atom.project?.destroy() atom.project = null + atom.themes.removeStylesheet('global-editor-styles') + delete atom.state.packageStates $('#jasmine-content').empty() unless window.debugContent diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 082244f83..92092cc90 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -20,7 +20,6 @@ describe "WorkspaceView", -> waitsForPromise -> atom.workspace.open(pathToOpen) - describe "@deserialize()", -> viewState = null @@ -294,3 +293,24 @@ describe "WorkspaceView", -> expect(atom.workspaceView).toHaveClass 'scrollbars-visible-always' scrollbarStyle.emitValue 'overlay' expect(atom.workspaceView).toHaveClass 'scrollbars-visible-when-scrolling' + + describe "editor font styling", -> + editorNode = null + + beforeEach -> + atom.workspaceView.attachToDom() + editorNode = atom.workspaceView.find('.editor')[0] + + it "updates the font-size based on the 'editor.fontSize' config value", -> + expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px' + atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5) + expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px' + + it "updates the font-family based on the 'editor.fontFamily' config value", -> + expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily') + atom.config.set('editor.fontFamily', 'sans-serif') + expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily') + + it "updates the line-height based on the 'editor.lineHeight' config value", -> + atom.config.set('editor.lineHeight', '20px') + expect(getComputedStyle(editorNode).lineHeight).toBe atom.config.get('editor.lineHeight') diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 7eea4a516..22e3c1ab5 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -513,9 +513,6 @@ EditorComponent = React.createClass parentView.command command, listener observeConfig: -> - @subscribe atom.config.observe 'editor.fontFamily', @setFontFamily - @subscribe atom.config.observe 'editor.fontSize', @setFontSize - @subscribe atom.config.observe 'editor.lineHeight', @setLineHeight @subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide @subscribe atom.config.observe 'editor.invisibles', @setInvisibles @subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index f055a0d45..8ad7511aa 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -96,6 +96,11 @@ class WorkspaceView extends View when 'overlay' @addClass("scrollbars-visible-when-scrolling") + + @subscribe atom.config.observe 'editor.fontSize', @setEditorFontSize + @subscribe atom.config.observe 'editor.fontFamily', @setEditorFontFamily + @subscribe atom.config.observe 'editor.lineHeight', @setEditorLineHeight + @updateTitle() @on 'focus', (e) => @handleFocus(e) @@ -340,6 +345,24 @@ class WorkspaceView extends View beforeRemove: -> @model.destroy() + setEditorFontSize: (fontSize) => + @setEditorStyle('font-size', fontSize + 'px') + + setEditorFontFamily: (fontFamily) => + @setEditorStyle('font-family', fontFamily) + + setEditorLineHeight: (lineHeight) => + @setEditorStyle('line-height', lineHeight) + + setEditorStyle: (property, value) -> + unless styleNode = atom.themes.stylesheetElementForId('global-editor-styles')[0] + atom.themes.applyStylesheet('global-editor-styles', '.editor {}') + styleNode = atom.themes.stylesheetElementForId('global-editor-styles')[0] + + editorRule = styleNode.sheet.cssRules[0] + editorRule.style[property] = value + atom.themes.emit 'stylesheets-changed' + # Deprecated eachPane: (callback) -> deprecate("Use WorkspaceView::eachPaneView instead") From bd77a022072176fff83cfea59a1880e77d49f405 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 28 Jul 2014 15:43:17 -0600 Subject: [PATCH 4/4] Measure DOM in EditorComponent when a stylesheet is updated --- spec/workspace-view-spec.coffee | 11 +++++++++-- src/editor-component.coffee | 2 +- src/workspace-view.coffee | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 92092cc90..44dbc2428 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -295,22 +295,29 @@ describe "WorkspaceView", -> expect(atom.workspaceView).toHaveClass 'scrollbars-visible-when-scrolling' describe "editor font styling", -> - editorNode = null + [editorNode, editor] = [] beforeEach -> atom.workspaceView.attachToDom() editorNode = atom.workspaceView.find('.editor')[0] + editor = atom.workspaceView.find('.editor').view().getEditor() it "updates the font-size based on the 'editor.fontSize' config value", -> + initialCharWidth = editor.getDefaultCharWidth() expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px' atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5) expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px' + expect(editor.getDefaultCharWidth()).toBeGreaterThan initialCharWidth it "updates the font-family based on the 'editor.fontFamily' config value", -> + initialCharWidth = editor.getDefaultCharWidth() expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily') atom.config.set('editor.fontFamily', 'sans-serif') expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily') + expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth it "updates the line-height based on the 'editor.lineHeight' config value", -> - atom.config.set('editor.lineHeight', '20px') + initialLineHeight = editor.getLineHeightInPixels() + atom.config.set('editor.lineHeight', '30px') expect(getComputedStyle(editorNode).lineHeight).toBe atom.config.get('editor.lineHeight') + expect(editor.getLineHeightInPixels()).not.toBe initialLineHeight diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 22e3c1ab5..fd01764de 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -176,7 +176,7 @@ EditorComponent = React.createClass @listenForDOMEvents() @listenForCommands() - @subscribe atom.themes, 'stylesheet-added stylsheet-removed', @onStylesheetsChanged + @subscribe atom.themes, 'stylesheet-added stylesheet-removed stylesheet-updated', @onStylesheetsChanged @subscribe scrollbarStyle.changes, @refreshScrollbars if @visible = @isVisible() diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 8ad7511aa..4206e9f83 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -359,8 +359,10 @@ class WorkspaceView extends View atom.themes.applyStylesheet('global-editor-styles', '.editor {}') styleNode = atom.themes.stylesheetElementForId('global-editor-styles')[0] - editorRule = styleNode.sheet.cssRules[0] + {sheet} = styleNode + editorRule = sheet.cssRules[0] editorRule.style[property] = value + atom.themes.emit 'stylesheet-updated', sheet atom.themes.emit 'stylesheets-changed' # Deprecated