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;
+}