diff --git a/lib/main.coffee b/lib/main.coffee
index c700829..40bc785 100644
--- a/lib/main.coffee
+++ b/lib/main.coffee
@@ -118,11 +118,13 @@ module.exports =
renderer ?= require './renderer'
text = editor.getSelectedText() or editor.getText()
- renderer.toHTML text, editor.getPath(), editor.getGrammar(), (error, html) ->
- if error
- console.warn('Copying Markdown as HTML failed', error)
- else
- atom.clipboard.write(html)
+ new Promise (resolve) ->
+ renderer.toHTML text, editor.getPath(), editor.getGrammar(), (error, html) ->
+ if error
+ console.warn('Copying Markdown as HTML failed', error)
+ else
+ atom.clipboard.write(html)
+ resolve()
saveAsHTML: ->
activePaneItem = atom.workspace.getActivePaneItem()
diff --git a/lib/markdown-preview-view.coffee b/lib/markdown-preview-view.coffee
index 6b3d7fb..9e6105d 100644
--- a/lib/markdown-preview-view.coffee
+++ b/lib/markdown-preview-view.coffee
@@ -330,20 +330,21 @@ class MarkdownPreviewView
if filePath
title = path.parse(filePath).name
- @getHTML (error, htmlBody) =>
- if error?
- throw error
- else
- html = """
-
-
-
-
- #{title}
-
-
- #{htmlBody}
- """ + "\n" # Ensure trailing newline
-
- fs.writeFileSync(htmlFilePath, html)
- atom.workspace.open(htmlFilePath)
+ new Promise (resolve, reject) =>
+ @getHTML (error, htmlBody) =>
+ if error?
+ throw error
+ else
+ html = """
+
+
+
+
+ #{title}
+
+
+ #{htmlBody}
+ """ + "\n" # Ensure trailing newline
+
+ fs.writeFileSync(htmlFilePath, html)
+ atom.workspace.open(htmlFilePath).then(resolve)
diff --git a/lib/renderer.coffee b/lib/renderer.coffee
index 5ee28d0..382a021 100644
--- a/lib/renderer.coffee
+++ b/lib/renderer.coffee
@@ -1,8 +1,8 @@
+{TextEditor} = require 'atom'
path = require 'path'
cheerio = require 'cheerio'
createDOMPurify = require 'dompurify'
fs = require 'fs-plus'
-Highlights = require 'highlights'
roaster = null # Defer until used
{scopeForFenceName} = require './extension-helper'
@@ -11,25 +11,22 @@ highlighter = null
packagePath = path.dirname(__dirname)
exports.toDOMFragment = (text='', filePath, grammar, callback) ->
- render text, filePath, (error, html) ->
+ render text, filePath, (error, domFragment) ->
return callback(error) if error?
-
- template = document.createElement('template')
- template.innerHTML = html
- domFragment = template.content.cloneNode(true)
-
- # Default code blocks to be coffee in Literate CoffeeScript files
- defaultCodeLanguage = 'coffee' if grammar?.scopeName is 'source.litcoffee'
- convertCodeBlocksToAtomEditors(domFragment, defaultCodeLanguage)
- callback(null, domFragment)
+ highlightCodeBlocks(domFragment, grammar, makeAtomEditorNonInteractive).then ->
+ callback(null, domFragment)
exports.toHTML = (text='', filePath, grammar, callback) ->
- render text, filePath, (error, html) ->
+ render text, filePath, (error, domFragment) ->
return callback(error) if error?
- # Default code blocks to be coffee in Literate CoffeeScript files
- defaultCodeLanguage = 'coffee' if grammar?.scopeName is 'source.litcoffee'
- html = tokenizeCodeBlocks(html, defaultCodeLanguage)
- callback(null, html)
+
+ div = document.createElement('div')
+ div.appendChild(domFragment)
+ document.body.appendChild(div)
+
+ highlightCodeBlocks(div, grammar, convertAtomEditorToStandardElement).then ->
+ callback(null, div.innerHTML)
+ div.remove()
render = (text, filePath, callback) ->
roaster ?= require 'roaster'
@@ -45,14 +42,17 @@ render = (text, filePath, callback) ->
return callback(error) if error?
html = createDOMPurify().sanitize(html, {ALLOW_UNKNOWN_PROTOCOLS: atom.config.get('markdown-preview.allowUnsafeProtocols')})
- html = resolveImagePaths(html, filePath)
- callback(null, html.trim())
-resolveImagePaths = (html, filePath) ->
+ template = document.createElement('template')
+ template.innerHTML = html.trim()
+ fragment = template.content.cloneNode(true)
+
+ resolveImagePaths(fragment, filePath)
+ callback(null, fragment)
+
+resolveImagePaths = (element, filePath) ->
[rootDirectory] = atom.project.relativizePath(filePath)
- o = document.createElement('div')
- o.innerHTML = html
- for img in o.querySelectorAll('img')
+ for img in element.querySelectorAll('img')
# We use the raw attribute instead of the .src property because the value
# of the property seems to be transformed in some cases.
if src = img.getAttribute('src')
@@ -68,60 +68,55 @@ resolveImagePaths = (html, filePath) ->
else
img.src = path.resolve(path.dirname(filePath), src)
- o.innerHTML
+highlightCodeBlocks = (domFragment, grammar, editorCallback) ->
+ if grammar?.scopeName is 'source.litcoffee'
+ defaultLanguage = 'coffee'
+ else
+ defaultLanguage = 'text'
-convertCodeBlocksToAtomEditors = (domFragment, defaultLanguage='text') ->
if fontFamily = atom.config.get('editor.fontFamily')
for codeElement in domFragment.querySelectorAll('code')
codeElement.style.fontFamily = fontFamily
+ promises = []
for preElement in domFragment.querySelectorAll('pre')
- codeBlock = preElement.firstElementChild ? preElement
- fenceName = codeBlock.getAttribute('class')?.replace(/^lang-/, '') ? defaultLanguage
-
- editorElement = document.createElement('atom-text-editor')
-
- preElement.parentNode.insertBefore(editorElement, preElement)
- preElement.remove()
-
+ do (preElement) ->
+ codeBlock = preElement.firstElementChild ? preElement
+ fenceName = codeBlock.getAttribute('class')?.replace(/^lang-/, '') ? defaultLanguage
+ preElement.classList.add('editor-colors', "lang-#{fenceName}")
+ editor = new TextEditor({readonly: true, keyboardInputEnabled: false})
+ editorElement = editor.getElement()
+ editorElement.setUpdatedSynchronously(true)
+ preElement.innerHTML = ''
+ preElement.parentNode.insertBefore(editorElement, preElement)
+ editor.setText(codeBlock.textContent.replace(/\r?\n$/, ''))
+ atom.grammars.assignLanguageMode(editor, scopeForFenceName(fenceName))
+ editor.setVisible(true)
+ promises.push(editorCallback(editorElement, preElement))
+ Promise.all(promises)
+
+makeAtomEditorNonInteractive = (editorElement, preElement) ->
+ preElement.remove()
+ editorElement.setAttributeNode(document.createAttribute('gutter-hidden')) # Hide gutter
+ editorElement.removeAttribute('tabindex') # Make read-only
+
+ # Remove line decorations from code blocks.
+ for cursorLineDecoration in editorElement.getModel().cursorLineDecorations
+ cursorLineDecoration.destroy()
+ return
+
+convertAtomEditorToStandardElement = (editorElement, preElement) ->
+ new Promise (resolve) ->
+ done = ->
+ for line in editorElement.querySelectorAll('.line:not(.dummy)')
+ line2 = document.createElement('div')
+ line2.className = 'line'
+ line2.innerHTML = line.firstChild.innerHTML
+ preElement.appendChild(line2)
+ editorElement.remove()
+ resolve()
editor = editorElement.getModel()
- lastNewlineIndex = codeBlock.textContent.search(/\r?\n$/)
- editor.setText(codeBlock.textContent.substring(0, lastNewlineIndex)) # Do not include a trailing newline
- editorElement.setAttributeNode(document.createAttribute('gutter-hidden')) # Hide gutter
- editorElement.removeAttribute('tabindex') # Make read-only
-
- if grammar = atom.grammars.grammarForScopeName(scopeForFenceName(fenceName))
- editor.setGrammar(grammar)
-
- # Remove line decorations from code blocks.
- for cursorLineDecoration in editor.cursorLineDecorations
- cursorLineDecoration.destroy()
-
- domFragment
-
-tokenizeCodeBlocks = (html, defaultLanguage='text') ->
- o = document.createElement('div')
- o.innerHTML = html
-
- if fontFamily = atom.config.get('editor.fontFamily')
- for codeElement in o.querySelectorAll('code')
- codeElement.style['font-family'] = fontFamily
-
- for preElement in o.querySelectorAll("pre")
- codeBlock = preElement.children[0]
- fenceName = codeBlock.className?.replace(/^lang-/, '') ? defaultLanguage
-
- highlighter ?= new Highlights(registry: atom.grammars, scopePrefix: 'syntax--')
- highlightedHtml = highlighter.highlightSync
- fileContents: codeBlock.textContent
- scopeName: scopeForFenceName(fenceName)
-
- highlightedBlock = document.createElement('pre')
- highlightedBlock.innerHTML = highlightedHtml
- # The `editor` class messes things up as `.editor` has absolutely positioned lines
- highlightedBlock.children[0].classList.remove('editor')
- highlightedBlock.children[0].classList.add("lang-#{fenceName}")
-
- preElement.outerHTML = highlightedBlock.innerHTML
-
- o.innerHTML
+ if editor.getBuffer().getLanguageMode().fullyTokenized
+ done()
+ else
+ editor.onDidTokenize(done)
diff --git a/package.json b/package.json
index 767b7c5..5612bd5 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,6 @@
"dependencies": {
"dompurify": "^1.0.2",
"fs-plus": "^3.0.0",
- "highlights": "^3.1.1",
"roaster": "^1.2.1",
"underscore-plus": "^1.0.0"
},
diff --git a/spec/fixtures/saved-html.html b/spec/fixtures/saved-html.html
index 682365b..eb592b4 100644
--- a/spec/fixtures/saved-html.html
+++ b/spec/fixtures/saved-html.html
@@ -11,6 +11,6 @@
pre.editor-colors .hr { background: url('data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC'); }
Code Block
-if a === 3 {
b = 5
}
+if a === 3 {
b = 5
}
encoding → issue