Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[markdown-preview] Optimize re-rendering of content in a preview pane… #984

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions packages/markdown-preview/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ const isMarkdownPreviewView = function (object) {
}

module.exports = {
activate () {
activate() {
this.disposables = new CompositeDisposable()
this.commandSubscriptions = new CompositeDisposable()

this.style = new CSSStyleSheet()

// When we upgrade Electron, we can push onto `adoptedStyleSheets`
savetheclocktower marked this conversation as resolved.
Show resolved Hide resolved
// directly. For now, we have to do this silly thing.
let styleSheets = Array.from(document.adoptedStyleSheets ?? [])
styleSheets.push(this.style)
document.adoptedStyleSheets = styleSheets

this.disposables.add(
atom.config.observe('markdown-preview.grammars', grammars => {
this.commandSubscriptions.dispose()
Expand Down Expand Up @@ -53,6 +61,22 @@ module.exports = {
})
)

this.disposables.add(
atom.config.observe('editor.fontFamily', (fontFamily) => {
// Keep the user's `fontFamily` setting in sync with preview styles.
// `pre` blocks will use this font automatically, but `code` elements
// need a specific style rule.
//
// Since this applies to all content, we should declare this only once,
// instead of once per preview view.
this.style.replaceSync(`
.markdown-preview code {
font-family: ${fontFamily} !important;
}
`)
})
)

const previewFile = this.previewFile.bind(this)
for (const extension of [
'markdown',
Expand Down Expand Up @@ -94,12 +118,12 @@ module.exports = {
)
},

deactivate () {
deactivate() {
this.disposables.dispose()
this.commandSubscriptions.dispose()
},

createMarkdownPreviewView (state) {
createMarkdownPreviewView(state) {
if (state.editorId || fs.isFileSync(state.filePath)) {
if (MarkdownPreviewView == null) {
MarkdownPreviewView = require('./markdown-preview-view')
Expand All @@ -108,7 +132,7 @@ module.exports = {
}
},

toggle () {
toggle() {
if (isMarkdownPreviewView(atom.workspace.getActivePaneItem())) {
atom.workspace.destroyActivePaneItem()
return
Expand All @@ -129,11 +153,11 @@ module.exports = {
}
},

uriForEditor (editor) {
uriForEditor(editor) {
return `markdown-preview://editor/${editor.id}`
},

removePreviewForEditor (editor) {
removePreviewForEditor(editor) {
const uri = this.uriForEditor(editor)
const previewPane = atom.workspace.paneForURI(uri)
if (previewPane != null) {
Expand All @@ -144,7 +168,7 @@ module.exports = {
}
},

addPreviewForEditor (editor) {
addPreviewForEditor(editor) {
const uri = this.uriForEditor(editor)
const previousActivePane = atom.workspace.getActivePane()
const options = { searchAllPanes: true }
Expand All @@ -161,7 +185,7 @@ module.exports = {
})
},

previewFile ({ target }) {
previewFile({ target }) {
const filePath = target.dataset.path
if (!filePath) {
return
Expand All @@ -178,7 +202,7 @@ module.exports = {
})
},

async copyHTML () {
async copyHTML() {
const editor = atom.workspace.getActiveTextEditor()
if (editor == null) {
return
Expand All @@ -191,13 +215,14 @@ module.exports = {
const html = await renderer.toHTML(
text,
editor.getPath(),
editor.getGrammar()
editor.getGrammar(),
editor.id
)

atom.clipboard.write(html)
},

saveAsHTML () {
saveAsHTML() {
const activePaneItem = atom.workspace.getActivePaneItem()
if (isMarkdownPreviewView(activePaneItem)) {
atom.workspace.getActivePane().saveItemAs(activePaneItem)
Expand Down
58 changes: 45 additions & 13 deletions packages/markdown-preview/lib/markdown-preview-view.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const path = require('path')
const morphdom = require('morphdom')

const { Emitter, Disposable, CompositeDisposable, File } = require('atom')
const _ = require('underscore-plus')
Expand All @@ -17,6 +18,7 @@ module.exports = class MarkdownPreviewView {
this.element = document.createElement('div')
this.element.classList.add('markdown-preview')
this.element.tabIndex = -1

this.emitter = new Emitter()
this.loaded = false
this.disposables = new CompositeDisposable()
Expand All @@ -32,6 +34,7 @@ module.exports = class MarkdownPreviewView {
})
)
}
this.editorCache = new renderer.EditorCache(editorId)
}

serialize() {
Expand All @@ -52,6 +55,7 @@ module.exports = class MarkdownPreviewView {
destroy() {
this.disposables.dispose()
this.element.remove()
this.editorCache.destroy()
}

registerScrollCommands() {
Expand Down Expand Up @@ -83,7 +87,7 @@ module.exports = class MarkdownPreviewView {
return this.emitter.on('did-change-title', callback)
}

onDidChangeModified(callback) {
onDidChangeModified(_callback) {
// No op to suppress deprecation warning
return new Disposable()
}
Expand Down Expand Up @@ -270,7 +274,22 @@ module.exports = class MarkdownPreviewView {
return this.getMarkdownSource()
.then(source => {
if (source != null) {
return this.renderMarkdownText(source)
if (this.loaded) {
return this.renderMarkdownText(source);
} else {
// If we haven't loaded yet, defer before we render the Markdown
// for the first time. This allows the pane to appear and to
// display the loading indicator. Otherwise the first render
// happens before the pane is even visible.
//
// This doesn't slow anything down; it just shifts the work around
// so that the pane appears earlier in the cycle.
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.renderMarkdownText(source))
}, 0)
savetheclocktower marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
})
.catch(reason => this.showError({ message: reason }))
Expand Down Expand Up @@ -309,18 +328,34 @@ module.exports = class MarkdownPreviewView {

async renderMarkdownText(text) {
const { scrollTop } = this.element

try {
const domFragment = await renderer.toDOMFragment(
const [domFragment, done] = await renderer.toDOMFragment(
text,
this.getPath(),
this.getGrammar()
this.getGrammar(),
this.editorId
)

this.loading = false
this.loaded = true
this.element.textContent = ''
this.element.appendChild(domFragment)

// Clone the existing container
let newElement = this.element.cloneNode(false)
newElement.appendChild(domFragment)

morphdom(this.element, newElement, {
onBeforeNodeDiscarded(node) {
// Don't discard `atom-text-editor` elements despite the fact that
// they don't exist in the new content.
if (node.nodeName === 'ATOM-TEXT-EDITOR') {
return false
}
}
})

await done(this.element)
this.element.classList.remove('loading')

this.emitter.emit('did-change-markdown')
this.element.scrollTop = scrollTop
} catch (error) {
Expand Down Expand Up @@ -400,7 +435,7 @@ module.exports = class MarkdownPreviewView {
.join('\n')
.replace(/atom-text-editor/g, 'pre.editor-colors')
.replace(/:host/g, '.host') // Remove shadow-dom :host selector causing problem on FF
.replace(cssUrlRegExp, function (match, assetsName, offset, string) {
.replace(cssUrlRegExp, function (_match, assetsName, _offset, _string) {
// base64 encode assets
const assetPath = path.join(__dirname, '../assets', assetsName)
const originalData = fs.readFileSync(assetPath, 'binary')
Expand All @@ -413,6 +448,7 @@ module.exports = class MarkdownPreviewView {

showError(result) {
this.element.textContent = ''
this.element.classList.remove('loading')
const h2 = document.createElement('h2')
h2.textContent = 'Previewing Markdown Failed'
this.element.appendChild(h2)
Expand All @@ -425,11 +461,7 @@ module.exports = class MarkdownPreviewView {

showLoading() {
this.loading = true
this.element.textContent = ''
const div = document.createElement('div')
div.classList.add('markdown-spinner')
div.textContent = 'Loading Markdown\u2026'
this.element.appendChild(div)
this.element.classList.add('loading')
}

selectAll() {
Expand Down
Loading
Loading