diff --git a/lib/previewing.js b/lib/previewing.js index 34fe7ba..19a3d9d 100644 --- a/lib/previewing.js +++ b/lib/previewing.js @@ -1,6 +1,7 @@ "use strict"; const {Disposable} = require("atom"); +const canvasStyles = require.resolve("roff/lib/postproc/canvas/viewer.css"); const fontStyles = require.resolve("urw-core35-fonts/index.css"); const {getManPrompt} = require("./utils.js"); const {parseManURL, resolveManRef} = require("roff"); @@ -8,11 +9,12 @@ const {parseManURL, resolveManRef} = require("roff"); module.exports = () => [ attachStyleSheet(fontStyles), + attachStyleSheet(canvasStyles), atom.workspace.addOpener(uri => { const page = parseManURL(uri); if(null !== page) - return; // TODO: Reconnect once adapters are finished + return new (require("./views/manpage-view.js"))(page); }), atom.commands.add("body", { @@ -74,13 +76,13 @@ function notifyNothingMatched(name, section = ""){ * @internal */ async function runOpenCmd(){ - const input = await getManPrompt().promptUser({ + const input = resolveManRef(await getManPrompt().promptUser({ headerText: "Enter the name of a manual-page", - footerHTML: "E.g., perl, 5 grep, grep(5)"}); + footerHTML: "E.g., perl, 5 grep, grep(5)"})); return input - ? atom.workspace.open((console.log(page), page.toURL())) - : notifyNothingMatched(page.name, page.section); + ? atom.workspace.open(`man://${input.join("/")}`) + : notifyNothingMatched(...input); } diff --git a/lib/utils.js b/lib/utils.js index a5de359..c882631 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,7 @@ let promptView = null; let manAdapter = null; +let grogAdapter = null; let groffAdapter = null; @@ -22,6 +23,21 @@ module.exports = { }, + /** + * Retrieve adapter instance for guessing groff(1) formatting options. + * + * @return {GrogAdapter} + * @internal + */ + async getGrogAdapter(){ + if(null === grogAdapter){ + const {GrogAdapter} = require("roff"); + grogAdapter = await GrogAdapter.loadDefault(); + } + return grogAdapter; + }, + + /** * Retrieve adapter instance for integrating with groff(1). * diff --git a/lib/views/manpage-view.js b/lib/views/manpage-view.js index f6815c1..7684dd3 100644 --- a/lib/views/manpage-view.js +++ b/lib/views/manpage-view.js @@ -1,13 +1,69 @@ "use strict"; +const {TroffCanvasViewer, TTYViewer} = require("roff"); +const {getManAdapter, getGrogAdapter, getGroffAdapter} = require("../utils.js"); + + +/** + * Wrapper class which displays rendered previews of Roff documents. + * @internal + * @class + */ class ManpageView{ - constructor(){ + constructor(page = {}){ + Object.assign(this, page); + this.element = document.createElement("div"); + this.canvasView = new TroffCanvasViewer({parentElement: this.element}); + this.ttyView = new TTYViewer(document.createElement("pre")); + this.ttyView.element.className = "troff-tty"; + let mode = ""; + Object.defineProperties(this, { + currentView: { + get: () => "canvas" === mode + ? this.canvasView + : this.ttyView, + }, + + currentElement: { + get: () => this.currentView.root || this.currentView.element, + }, + + mode: { + get: () => mode, + set: to => { + if(mode === (to = to.trim().toLowerCase())) return; + switch(to){ + case "canvas": + this.ttyView.element.remove(); + this.element.appendChild(this.canvasView.root); + mode = to; + this.refresh(); + break; + + // FIXME: Nuke the entire TTYViewer class + case "tty": + this.canvasView.root.remove(); + this.element.appendChild(this.ttyView.element); + mode = to; + Object.assign(this.ttyView.element.style, { + fontSize: atom.config.get("editor.fontSize") + "px", + fontFamily: atom.config.get("editor.fontFamily"), + lineHeight: atom.config.get("editor.lineHeight"), + }); + this.refresh(); + break; + } + }, + }, + }); + this.mode = "canvas"; } + getIconName(){ - return "manpage-icon"; + return "manpage"; } @@ -17,8 +73,38 @@ class ManpageView{ } + getTitle(){ + if(!this.name) return "Untitled"; + let title = this.name; + if(this.section) title += `(${this.section})`; + return title; + } + + getPath(){ + return this.path || ""; + } + + + async refresh(){ + const groff = await getGroffAdapter(); + if(groff){ + const grog = await getGrogAdapter(); + const man = await getManAdapter(); + this.path = (await man.find(this.name, this.section))[0]; + this.source = await man.load(this.path); + const device = "canvas" === this.mode + ? groff.devices.pdf ? "pdf" : "ps" + : groff.devices.utf8 ? "utf8" : "ascii"; + const tokens = await groff.format(this.source, device, { + args: (await grog.guessOptions(this.source)).pop().slice(1), + raw: true, + }); + "canvas" === this.mode + ? this.currentView.load(tokens) + : this.currentView.render(tokens); + } } } diff --git a/styles/styles.less b/styles/styles.less index 9125d85..86db64e 100644 --- a/styles/styles.less +++ b/styles/styles.less @@ -1,4 +1,49 @@ +@import "syntax-variables"; + // Fix that sodding highlighting in error notifications atom-notifications atom-notification.error pre > code{ background: inherit; } + +// Tab-icons for ManpageViews +.icon-manpage{ + body:not(.file-icons-coloured):not(.file-icons-colourless) &::before{ + font-family: "Octicons Regular"; + font-size: 16px; + top: 1px; + content: "\f007"; + } + + body.file-icons-coloured &::before, + body.file-icons-colourless &::before{ + content: "\e936"; + font-family: file-icons; + font-size: 15px; + top: 3px; + } + + body.file-icons-coloured &::before{ + color: #66783e; + } +} + +.troff-view-pages{ + padding: 1em; +} +.troff-view-page{ + margin-bottom: 1em; +} + +.troff-tty{ + background: @syntax-background-color; + color: @syntax-text-color; + + white-space: pre; + overflow-x: hidden; + overflow-y: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +}