Markdown Syntax Highlights (#2332)

* Basic code highlights.

* Check if the given language is valid, use auto highlight if not.

* Fix check.

* Set baseUrl as well.

Unrelated to code blocks, but fixes #1653 and is a single line change in the options I've added.

* Bump marked types package number.

* Move style sheet to only markdown preview.

* Swap theme to option.

* Fix review comment from previous PR.

* Fix nohighlights.

* Update tests for Markdown preview.

* Fix lint error.

* Add config option for syntax highlights.
This commit is contained in:
Ryan C 2018-07-26 10:45:08 +01:00 committed by GitHub
parent 759918d17b
commit b901ecdda0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 15 deletions

View File

@ -4,7 +4,7 @@
* Entry point for browser integration plugin
*/
import { ipcRenderer, shell, WebviewTag } from "electron"
import { shell, WebviewTag } from "electron"
import * as React from "react"
import * as Oni from "oni-api"
@ -290,10 +290,6 @@ export const activate = (
detail: "",
enabled: isBrowserScrollCommandEnabled,
})
ipcRenderer.on("open-oni-browser", (event: string, args: string) => {
openUrl(args)
})
}
export const registerAchievements = (achievements: AchievementsManager) => {

View File

@ -81,6 +81,8 @@ const BaseConfiguration: IConfigurationValues = {
"experimental.indentLines.bannedFiletypes": [],
"experimental.markdownPreview.enabled": false,
"experimental.markdownPreview.autoScroll": true,
"experimental.markdownPreview.syntaxHighlights": true,
"experimental.markdownPreview.syntaxTheme": "atom-one-dark",
"experimental.neovim.transport": "stdio",
// TODO: Enable pipe transport for Windows

View File

@ -65,6 +65,8 @@ export interface IConfigurationValues {
// Whether the markdown preview pane should be shown
"experimental.markdownPreview.enabled": boolean
"experimental.markdownPreview.autoScroll": boolean
"experimental.markdownPreview.syntaxHighlights": boolean
"experimental.markdownPreview.syntaxTheme": string
// The transport to use for Neovim
// Valid values are "stdio" and "pipe"

View File

@ -23,6 +23,7 @@
"tbd-dependencies": {
"marked": "^0.4.0",
"dompurify": "1.0.2",
"highlight.js": "^9.12.0",
"oni-types": "^0.0.4",
"oni-api": "^0.0.9"
},
@ -31,6 +32,7 @@
"rimraf": "^2.6.2"
},
"tbd-devDependencies": {
"@types/highlight.js": "^9.12.2",
"typescript": "2.5.3",
"rimraf": "2.6.2"
}

View File

@ -1,6 +1,7 @@
import { EventCallback, IDisposable, IEvent } from "oni-types"
import * as dompurify from "dompurify"
import * as hljs from "highlight.js"
import * as marked from "marked"
import * as Oni from "oni-api"
import * as React from "react"
@ -84,6 +85,9 @@ class MarkdownPreview extends React.PureComponent<IMarkdownPreviewProps, IMarkdo
private generateContainerStyle(): string {
const colors = this.state.colors
const syntaxHighlightTheme = this.props.oni.configuration.getValue(
"experimental.markdownPreview.syntaxTheme",
)
const codeBlockStyle = `
background: ${colors.codeBackground};
@ -95,6 +99,8 @@ class MarkdownPreview extends React.PureComponent<IMarkdownPreviewProps, IMarkdo
`
return `
<link rel="stylesheet" href="node_modules/highlight.js/styles/${syntaxHighlightTheme}.css">
<style>
.oniPluginMarkdownPreviewContainerStyle {
padding: 1em 1em 1em 1em;
@ -143,6 +149,38 @@ class MarkdownPreview extends React.PureComponent<IMarkdownPreviewProps, IMarkdo
markdownLines.splice(0, 0, generateAnchor(i))
markdownLines.push(generateAnchor(originalLinesCount - 1))
const markedOptions = {
baseUrl: this.props.oni.workspace.activeWorkspace,
highlight(code, lang) {
return code
},
}
const highlightsEnabled = this.props.oni.configuration.getValue(
"experimental.markdownPreview.syntaxHighlights",
)
if (highlightsEnabled) {
markedOptions.highlight = (code, lang) => {
const languageExists = hljs.getLanguage(lang)
const languageNotDefinedOrInvalid =
typeof lang === "undefined" ||
(typeof languageExists === "undefined" && lang !== "nohighlight")
if (languageNotDefinedOrInvalid) {
return hljs.highlightAuto(code).value
}
if (lang === "nohighlight") {
return code
}
return hljs.highlight(lang, code).value
}
}
marked.setOptions(markedOptions)
return marked(markdownLines.join("\n"))
}

View File

@ -258,7 +258,7 @@ export function createWindow(
if (!isDevelopment) {
currentWindow.webContents.on("will-navigate", (event, url) => {
event.preventDefault()
currentWindow.webContents.send("open-oni-browser", url)
currentWindow.webContents.send("execute-command", "browser.openUrl", url)
})
}

View File

@ -699,6 +699,7 @@
"electron-settings": "^3.1.4",
"find-up": "2.1.0",
"fs-extra": "^5.0.0",
"highlight.js": "^9.12.0",
"json5": "^1.0.1",
"keyboard-layout": "^2.0.13",
"marked": "^0.4.0",
@ -734,12 +735,13 @@
"@types/electron-settings": "^3.1.1",
"@types/enzyme": "^3.1.8",
"@types/fs-extra": "^5.0.2",
"@types/highlight.js": "^9.12.2",
"@types/jest": "^22.1.3",
"@types/jsdom": "11.0.0",
"@types/json5": "^0.0.29",
"@types/lodash": "4.14.38",
"@types/lolex": "2.1.0",
"@types/marked": "^0.3.0",
"@types/marked": "^0.4.0",
"@types/minimatch": "3.0.1",
"@types/minimist": "1.1.29",
"@types/mkdirp": "0.3.29",

View File

@ -3,7 +3,13 @@
*/
import { Assertor } from "./Assert"
import { awaitEditorMode, getTemporaryFilePath, insertText, navigateToFile } from "./Common"
import {
awaitEditorMode,
getElementByClassName,
getTemporaryFilePath,
insertText,
navigateToFile,
} from "./Common"
import * as Oni from "oni-api"
@ -11,6 +17,7 @@ interface IMarkdownPreviewPlugin {
isPaneOpen(): boolean
getUnrenderedContent(): string
getRenderedContent(): string
toggle(): void
}
export const settings = {
@ -24,25 +31,91 @@ export async function test(oni: Oni.Plugin.Api) {
await oni.automation.waitForEditors()
// Wait for Plugin to be loaded
await oni.automation.waitFor(() => oni.plugins.loaded)
const markdownPlugin = oni.plugins.getPlugin(
"oni-plugin-markdown-preview",
) as IMarkdownPreviewPlugin
assert.defined(markdownPlugin, "plugin instance")
assert.assert(!markdownPlugin.isPaneOpen(), "Preview pane is not initially closed")
// Check its not open by default
assert.assert(!markdownPlugin.isPaneOpen(), "Preview pane is initially closed.")
await navigateToFile(getTemporaryFilePath("md"), oni)
// Check it opens when navigating into a markdown file.
const markdownFilePath = getTemporaryFilePath("md")
await navigateToFile(markdownFilePath, oni)
await oni.automation.waitFor(() => markdownPlugin.isPaneOpen())
assert.isEmpty(
markdownPlugin.getUnrenderedContent().trim(),
"Preview pane for empty Markdown buffer",
"Preview pane shown for Markdown buffer.",
)
// Check a Title is rendered correctly.
await insertText(oni, "# Title 1")
assert.contains(
markdownPlugin.getRenderedContent(),
">Title 1</h1>",
"Preview pane with rendered header element",
"Preview pane with renders header element.",
)
// Check syntax highlights are applied.
oni.automation.sendKeys("o")
await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "insert")
oni.automation.sendKeys("```<enter>")
oni.automation.sendKeys("const test = 'Oni'<enter>")
oni.automation.sendKeys("```")
oni.automation.sendKeys("<esc>")
await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "normal")
assert.contains(
markdownPlugin.getRenderedContent(),
"code>",
"Code block present in preview HTML.",
)
assert.assert(
getElementByClassName("hljs-keyword").innerText === "const",
"Syntax highlights present in preview HTML.",
)
// Finally, test swapping between files.
// Swapping to a non-MD file should close it.
// Swapping to an MD file should open it.
// Closing the MD preview manually should cause it to remain closed.
const nonMDFilePath = getTemporaryFilePath("ts")
await navigateToFile(nonMDFilePath, oni)
await oni.automation.waitFor(
() => oni.editors.activeEditor.activeBuffer.language === "typescript",
)
assert.assert(!markdownPlugin.isPaneOpen(), "Preview pane is closed for non-MD file.")
await navigateToFile(markdownFilePath, oni)
await oni.automation.waitFor(
() => oni.editors.activeEditor.activeBuffer.language === "markdown",
)
assert.assert(markdownPlugin.isPaneOpen(), "Preview pane opens for MD file.")
// Manually closing preview, should remain closed now.
markdownPlugin.toggle()
assert.assert(!markdownPlugin.isPaneOpen(), "Preview pane closes on toggle.")
await navigateToFile(nonMDFilePath, oni)
await oni.automation.waitFor(
() => oni.editors.activeEditor.activeBuffer.language === "typescript",
)
await navigateToFile(markdownFilePath, oni)
await oni.automation.waitFor(
() => oni.editors.activeEditor.activeBuffer.language === "markdown",
)
// Should remain closed when swapping back in.
assert.assert(
!markdownPlugin.isPaneOpen(),
"Preview pane remains closed for MD file when manually closed.",
)
}

View File

@ -110,6 +110,10 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/highlight.js@^9.12.2":
version "9.12.3"
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
"@types/jest@^22.1.3":
version "22.1.3"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.1.3.tgz#25da391935e6fac537551456f077ce03144ec168"
@ -134,9 +138,9 @@
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-2.1.0.tgz#7a53611a6c970574173a7d92ce9a0da000652725"
"@types/marked@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524"
"@types/marked@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.0.tgz#057a6165703e7419217f8ffc6887747f980b6315"
"@types/minimatch@*":
version "3.0.3"
@ -4855,6 +4859,10 @@ he@1.1.1, he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
highlight.js@^9.12.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"