├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── grammars
├── context.cson
├── desc.cson
├── ditroff.cson
├── gremlin.cson
├── ideal.cson
├── manref.cson
├── pic.cson
├── roff.cson
└── runoff.cson
├── index.js
├── keymaps
└── keys.cson
├── lib
├── conversion.js
├── editing.js
├── hyperlinks.js
├── previewing.js
├── utils.js
└── views
│ ├── template.html
│ └── troff-view.js
├── menus
└── roff.cson
├── package.json
├── settings
└── editor.cson
├── snippets
└── snippets.cson
├── spec
├── .mocharc.js
├── fixtures
│ ├── input
│ │ ├── art-1.pic
│ │ ├── art-2.pic
│ │ ├── art-3.pic
│ │ ├── art.pikchr
│ │ ├── colours.pikchr
│ │ ├── context.sqtroff
│ │ ├── ditroff.1
│ │ ├── ditroff.2
│ │ ├── ditroff.3
│ │ ├── do.roff
│ │ ├── escapes.roff
│ │ ├── font-BI.1
│ │ ├── font-BI.2
│ │ ├── font-BI.3
│ │ ├── font-BR.1
│ │ ├── font-BR.2
│ │ ├── font-BR.3
│ │ ├── font-IB.1
│ │ ├── font-IB.2
│ │ ├── font-IB.3
│ │ ├── font-IR.1
│ │ ├── font-IR.2
│ │ ├── font-IR.3
│ │ ├── font-RB.1
│ │ ├── font-RB.2
│ │ ├── font-RB.3
│ │ ├── font-RI.1
│ │ ├── font-RI.2
│ │ ├── font-RI.3
│ │ └── links.1
│ ├── output
│ │ ├── art-1.pic.json
│ │ ├── art-2.pic.json
│ │ ├── art-3.pic.json
│ │ ├── art.pikchr.json
│ │ ├── colours.pikchr.json
│ │ ├── ditroff.1.json
│ │ ├── ditroff.2.json
│ │ ├── ditroff.3.json
│ │ ├── do.roff.json
│ │ ├── escapes.roff.json
│ │ ├── font-BI.1.json
│ │ ├── font-BI.2.json
│ │ ├── font-BI.3.json
│ │ ├── font-BR.1.json
│ │ ├── font-BR.2.json
│ │ ├── font-BR.3.json
│ │ ├── font-IB.1.json
│ │ ├── font-IB.2.json
│ │ ├── font-IB.3.json
│ │ ├── font-IR.1.json
│ │ ├── font-IR.2.json
│ │ ├── font-IR.3.json
│ │ ├── font-RB.1.json
│ │ ├── font-RB.2.json
│ │ ├── font-RB.3.json
│ │ ├── font-RI.1.json
│ │ ├── font-RI.2.json
│ │ ├── font-RI.3.json
│ │ └── links.1.json
│ ├── prune
│ └── update
└── grammar-spec.js
└── styles
└── styles.less
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = false
8 | indent_style = tab
9 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@alhadis/eslint-config/atom",
3 | "rules": {
4 | "require-atomic-updates": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pikchr linguist-language=Pic
2 |
3 | # GitHub doesn't support these formats, so we abuse its language
4 | # classification system in the name of superior highlighting.
5 | *.ditroff linguist-language=Roff
6 | *.sqtroff linguist-language=Roff
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Mac OS/X
2 | .apdisk
3 | .AppleDB
4 | .AppleDesktop
5 | .AppleDouble
6 | .DS_Store
7 | .LSOverride
8 | ._*
9 |
10 |
11 | ## IDE-specific
12 | sftp-config.json
13 | .project
14 |
15 |
16 | ## Windows
17 | Desktop.ini
18 | Thumbs.db
19 | ehthumbs.db
20 | $RECYCLE.BIN/
21 | *.lnk
22 |
23 |
24 | ## Other
25 | error_log
26 | node_modules
27 | components
28 | npm-debug.log
29 | .apl.history
30 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | This project adheres to [Semantic Versioning](http://semver.org).
5 |
6 |
7 | [Staged]
8 | ------------------------------------------------------------------------
9 | * __Added:__ Support for SoftQuad Context (SQTroff intermediate output)
10 | * __Added:__ Highlighting for variable-length point-size adjustments
11 | * __Added:__ Highlighting for [OSC 8] terminal hyperlink device controls
12 | * __Added:__ Highlighting for [dpic] and [Pikchr] language extensions
13 | * __Added:__ Highlighting for new requests added in [Groff v1.23.0][2]
14 | * __Added:__ Embedded shell highlighting to `.pi`, `.pso`, and `.sy`
15 | * __Changed:__ `.device` requests now highlight device control functions
16 | * __Changed:__ Man page references now exclude leading `man:` prefixes
17 | * __Changed:__ Texinfo documents now exempted from man page hyperlinking
18 | * __Fixed:__ `{` not expanding into `\{\ … .\}` inside `.while` requests
19 | * __Fixed:__ Inconsistent command highlighting in `.do` requests
20 | * __Fixed:__ Highlighting of `\s(±NN` inconsistent with `\s±(NN`
21 | * __Fixed:__ Conditional requests not highlighting the commands they run
22 | * __Fixed:__ Parameter highlighting terminated at the letter `R` in Atom
23 | * __Fixed:__ refer(1)-like markup inside macro definitions now truncated
24 | * __Fixed:__ Various [inaccuracies][1] related to Roff modeline matching
25 | * __Improved:__ Handling of postprocessor input misclassified as Roff
26 | * __Improved:__ Colour of ASCII control characters in dark themes
27 | * __Improved:__ Accuracy and granularity of Pic highlighting
28 |
29 | [1]: https://github.com/github/linguist/pull/5271
30 | [2]: http://git.savannah.gnu.org/cgit/groff.git/tree/NEWS?id=888464a#n10
31 | [dpic]: https://gitlab.com/aplevich/dpic
32 | [OSC 8]: https://lists.gnu.org/archive/html/groff/2021-10/msg00000.html
33 | [pikchr]: https://pikchr.org/home/doc/trunk/doc/differences.md
34 |
35 |
36 | [v1.3.1]
37 | ------------------------------------------------------------------------
38 | **October 16th, 2020**
39 | * __Added:__ Editor command for evaluating selection with `groff -Tutf8`
40 | * __Added:__ `mdoc.template` and `man.template` as recognised Roff files
41 | * __Added:__ Recognition of `.\"`, `.Dt/.Dd` and `$Mdocdate$` in headers
42 | * __Added:__ Support for `.ditroff` files containing intermediate output
43 | * __Added:__ Highlighting of certain [C0 control codes][] in identifiers
44 | * __Added:__ Highlighting for new `.stringdown` and `.stringup` requests
45 | * __Added:__ Highlighting for Heirloom's `\U`, `\T`, and `\W` extensions
46 | * __Added:__ Highlighting for device control functions
47 | * __Changed:__ `Dt` and `Sh` macros now highlighted like `TH` and `SH`
48 | * __Changed:__ Enabled soft-wrapping in Roff documents by default
49 | * __Changed:__ `.while` requests now highlighted the same as `.if`/`.ie`
50 | * __Fixed:__ `ditroff(7)` commands only tokenised at the start of a line
51 | * __Fixed:__ Incorrect highlighting of man page links containing colons
52 | * __Fixed:__ Missing highlighting of escape sequences in some contexts
53 | * __Improved:__ Highlighting of `.Dd` macros and `$Mdocdate$` constructs
54 |
55 |
56 | [v1.3.0]
57 | ------------------------------------------------------------------------
58 | **December 31st, 2019**
59 | * __Added:__ Automatic expansion of `{` into `\{\ … .\}` in conditionals
60 | * __Added:__ Automatic suppression of closing quotes and parentheses
61 | * __Added:__ Editor command to replace blank lines in Roff documents
62 | * __Added:__ Highlighting for `gperl`, `glilypond`, and `gpinyin` macros
63 | * __Added:__ Highlighting for shell source embedded with `mdoc`'s `.Bd`
64 | * __Added:__ Man-page previewer and clickable page hyperlinks
65 | * __Added:__ Missing highlighting for numerous `\f` sequences
66 | * __Added:__ Snippet for inserting a bordered section heading
67 | * __Added:__ Support for sequences escaped by Groff's `\E`
68 | * __Added:__ Vastly improved highlighting for `eqn` and `grn` markup
69 | * __Fixed:__ Conversion commands not working for opened `.pic` files
70 | * __Fixed:__ Documents mangled when rendering preprocessed Pic output
71 | * __Fixed:__ Escape sequences not highlighted in register definitions
72 | * __Fixed:__ Highlighting for `\f` escapes not terminated correctly
73 | * __Fixed:__ User-defined character names not highlighted by `\(xx`
74 | * __Fixed:__ Macros starting with `.[` always assumed to be `refer(1)`
75 | * __Fixed:__ Macros starting with `cu-` mistaken for `.cu` requests
76 | * __Fixed:__ Snippets not working when hitting tab key
77 | * __Improved:__ Highlighting of arithmetic and comparison operators
78 | * __Removed:__ `.t` from associated file extensions
79 |
80 |
81 | [v1.2.2]
82 | ------------------------------------------------------------------------
83 | **February 26th, 2017**
84 | * __Added:__ Commands for converting Roff to other document formats
85 | * __Added:__ Highlighting for intermediate output and font descriptions
86 | * __Added:__ Intelligent indentation of control lines
87 | * __Added:__ Various obscure extensions to supported filetypes
88 | * __Fixed:__ Bugs with matching certain Vim/Emacs modelines
89 | * __Fixed:__ RUNOFF literal blocks not terminated at `.end lit`
90 | * __Fixed:__ Underline missing after `.cu` requests
91 | * __Fixed:__ Various bugs with highlighting escapes and Pic commands
92 |
93 |
94 | [v1.2.1]
95 | ------------------------------------------------------------------------
96 | **August 1st, 2016**
97 | Patch for several highlighting-related bugs.
98 |
99 | * __Added:__ Snippet for inserting 3-column table
100 | * __Fixed:__ Comments not highlighted without preceding whitespace
101 | * __Fixed:__ Escape sequences not detected inside GNU long-names
102 | * __Fixed:__ Escape sequences not recognised in string definitions
103 | * __Fixed:__ Inconsistent highlighting applied to interpolated arguments
104 | * __Fixed:__ Macros matched as substrings of longer names
105 | * __Fixed:__ String definitions unable to span multiple lines
106 | * __Improved:__ Handling of requests without whitespace
107 |
108 |
109 | [v1.2.0]
110 | ------------------------------------------------------------------------
111 | **July 29th, 2016**
112 | Support for Troff preprocessors and the venerable RUNOFF has been added,
113 | along with numerous other enhancements.
114 |
115 | * __Added:__ Full syntax highlighting for `mdoc` macros
116 | * __Added:__ Language support for RUNOFF
117 | * __Added:__ Missing support for GNU-style long-names (`\*[name]`)
118 | * __Added:__ Snippets for conditionals, definitions, and page templates
119 | * __Added:__ Support for chem, dformat, eqn, grap, pic, refer, and tbl
120 | * __Fixed:__ Alphabetic Vroff conditional not matched (`.if v`)
121 | * __Fixed:__ Decimals without leading digits not recognised
122 | * __Fixed:__ `.ig` macros not blanking out their contents
123 | * __Fixed:__ Point-size adjustments incorrectly highlighted
124 |
125 |
126 | [v1.1.0]
127 | ------------------------------------------------------------------------
128 | **July 20th, 2016**
129 | This release addresses a number of glaring bugs with highlighting fonts,
130 | and improves the lexical accuracy of the grammar's pattern-matching.
131 |
132 | * __Added:__ Support for all Groff-related features and extensions
133 | * __Fixed:__ Blank elements not permitted in 3-part title strings
134 | * __Fixed:__ Control lines not matched after `\fB` and its ilk
135 | * __Fixed:__ Escape sequences formatted like ordinary markup
136 | * __Fixed:__ Paragraph macros not resetting bold/italic formatting
137 | * __Fixed:__ Various edge cases with oddly-formed syntax
138 | * __Improved:__ Highlighting of escape sequences and generic macros
139 |
140 |
141 | [v1.0.0]
142 | ------------------------------------------------------------------------
143 | **July 17th, 2016**
144 | Initial release. Adds syntax highlighting and language support for Roff.
145 |
146 |
147 | [Referenced links]:_____________________________________________________
148 | [C0 control codes]: http://en.wikipedia.org/wiki/C0_and_C1_control_codes
149 | [Staged]: https://github.com/Alhadis/language-roff/compare/v1.3.1...HEAD
150 | [v1.3.1]: https://github.com/Alhadis/language-roff/releases/tag/v1.3.1
151 | [v1.3.0]: https://github.com/Alhadis/language-roff/releases/tag/v1.3.0
152 | [v1.2.2]: https://github.com/Alhadis/language-roff/releases/tag/v1.2.2
153 | [v1.2.1]: https://github.com/Alhadis/language-roff/releases/tag/v1.2.1
154 | [v1.2.0]: https://github.com/Alhadis/language-roff/releases/tag/v1.2.0
155 | [v1.1.0]: https://github.com/Alhadis/language-roff/releases/tag/v1.1.0
156 | [v1.0.0]: https://github.com/Alhadis/language-roff/releases/tag/v1.0.0
157 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2024, John Gardner
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Roff Language Support
2 | =====================
3 |
4 | Support for Unix manual pages (Troff/Groff) in Atom, as well as the entire Roff typesetting language.
5 |
6 | 
7 |
8 | See [**roff**(7)](https://man7.org/linux/man-pages/man7/roff.7.html) or [*"History of UNIX Manpages"*](http://manpages.bsd.lv/history.html) for a comprehensive rundown of the language's history.
9 |
10 |
11 | Coverage
12 | --------
13 | * Every macro package: [`man`], [`mdoc`], [`me`], [`ms`], [`mm`], [`mom`], [`www`]
14 | * Every preprocessor: [`chem`], [`dformat`], [`eqn`], [`grap`], [`ideal`], [`pic`], [`refer`], [`tbl`]
15 | * GNU Troff extensions
16 | * The ancient [RUNOFF] language, with support for [OpenVMS DSR]
17 | * Intermediate representations: [`ditroff`], SoftQuad Context
18 |
19 |
20 | [`man`]: https://man7.org/linux/man-pages/man7/groff_man.7.html
21 | [`mdoc`]: https://man.openbsd.org/mdoc.7
22 | [`me`]: https://man7.org/linux/man-pages/man7/groff_me.7.html
23 | [`ms`]: https://man7.org/linux/man-pages/man7/groff_ms.7.html
24 | [`mm`]: https://man7.org/linux/man-pages/man7/groff_mm.7.html
25 | [`mom`]: http://www.schaffter.ca/mom/
26 | [`www`]: https://man7.org/linux/man-pages/man7/groff_www.7.html
27 | [`chem`]: https://man7.org/linux/man-pages/man1/chem.1.html
28 | [`dformat`]: https://rbn.im/bell-labs/cm.bell-labs.com/cm/cs/cstr/142.ps.gz
29 | [`eqn`]: https://en.wikipedia.org/wiki/Eqn
30 | [`grap`]: https://rbn.im/bell-labs/cm.bell-labs.com/cm/cs/cstr/114.ps.gz
31 | [`ideal`]: http://man.cat-v.org/unix_8th/1/ideal
32 | [`pic`]: https://en.wikipedia.org/wiki/Pic_language
33 | [`refer`]: https://en.wikipedia.org/wiki/Refer_(software)
34 | [`tbl`]: https://en.wikipedia.org/wiki/Tbl
35 | [`ditroff`]: https://man7.org/linux/man-pages/man7/ditroff.7.html
36 | [RUNOFF]: https://github.com/bwarken/RUNOFF_historical/
37 | [OpenVMS DSR]: http://h20565.www2.hpe.com/hpsc/doc/public/display?docId=emr_na-c04623260
38 |
--------------------------------------------------------------------------------
/grammars/desc.cson:
--------------------------------------------------------------------------------
1 | name: "Roff (Device Description)"
2 | scopeName: "source.ditroff.desc"
3 | fileTypes: [
4 | "DESC"
5 | "DESC.big"
6 | "DESC.small"
7 | "DESC.in"
8 | "DESC.proto"
9 | "DESC.8400"
10 | "R.proto"
11 | "tab.37"
12 | "tab.450"
13 | "tab.450-12"
14 | "tab.lp"
15 | "tab.think"
16 | "text.enc"
17 | "devps/download"
18 | "devpdf/download"
19 | "devpdf/Foundry"
20 | ]
21 | firstLineMatch: "# This file has been generated with GNU afmtodit"
22 | patterns: [{
23 | # Modified pattern-set for obvious device-description files
24 | begin: "(?=^\\s*(?:fonts|res|hor|vert|unitwidth|biggestfont)(?:\\s|$))"
25 | end: "(?=A)B"
26 | patterns: [{
27 | name: "meta.charset.ditroff.desc"
28 | begin: "^(charset)\\s*$"
29 | end: "(?=A)B"
30 | beginCaptures:
31 | 1: name: "keyword.control.section.ditroff.desc"
32 | patterns: [
33 | {include: "#comment"}
34 | {match: "\\S+", name: "entity.name.glyph.ditroff.desc"}
35 | ]
36 | }, include: "#main"]
37 | }, include: "#main"]
38 |
39 | repository:
40 | main:
41 | patterns: [
42 | {include: "#foundry"}
43 | {include: "#comment"}
44 | {include: "#charset"}
45 | {include: "#fields"}
46 | {include: "#kernpairs"}
47 | {include: "#fontPath"}
48 | ]
49 |
50 | charset:
51 | name: "meta.charset.ditroff.desc"
52 | begin: "^(charset)\\s*$"
53 | end: "^(?=kernpairs|\\s*$)"
54 | beginCaptures:
55 | 1: name: "keyword.control.section.ditroff.desc"
56 | patterns: [{
57 | name: "meta.glyph.ditroff.desc"
58 | match: """(?x) ^
59 | \\s* ((---)|\\S+) # Name
60 | \\s+ ([-\\d]+(?:,[-\\d]+){0,5}) # Metrics
61 | \\s+ (\\d) # Glyph type
62 | \\s+ (0[Xx][0-9A-Fa-f]+|\\d+) # Code
63 | (?:\\s+(?!--)(\\S+))? # Entity name
64 | """
65 | captures:
66 | 1: name: "entity.name.glyph.ditroff.desc"
67 | 2: name: "punctuation.definition.unnamed.glyph.ditroff.desc"
68 | 3: patterns: [
69 | {match: "-?\\d+", name: "constant.numeric.integer.ditroff.desc"}
70 | {match: ",", name: "punctuation.delimiter.comma.ditroff.desc"}
71 | ]
72 | 4: name: "constant.numeric.integer.ditroff.desc"
73 | 5: name: "constant.numeric.integer.ditroff.desc"
74 | 6: name: "variable.other.ditroff.desc"
75 | },{
76 | name: "meta.glyph.alias.ditroff.desc"
77 | match: "^\\s*(\\S+)\\s+(\")(?=\\s|$)"
78 | captures:
79 | 1: name: "entity.type.var.ditroff.desc"
80 | 2: name: "keyword.operator.ditroff.desc"
81 | },{
82 | name: "comment.line.double-dash.ditroff.desc"
83 | begin: "(?<=\\s)--(?!-)"
84 | end: "(?=$)"
85 | beginCaptures:
86 | 0: name: "punctuation.definition.comment.ditroff.desc"
87 | }, include: "#comment"]
88 |
89 | comment:
90 | name: "comment.line.number-sign.ditroff.desc"
91 | begin: "#"
92 | end: "$"
93 | beginCaptures:
94 | 0: name: "punctuation.definition.comment.ditroff.desc"
95 |
96 | fields:
97 | patterns: [{
98 | name: "meta.$1-list.ditroff.desc"
99 | begin: "^\\s*(ligatures|sizes)(?=\\s)"
100 | end: "(?<=\\s)0(?=\\s*$)|(?=^(?!\\s*$)(?!\\s*[\\d#]))"
101 | beginCaptures:
102 | 1: name: "entity.type.var.ditroff.desc"
103 | endCaptures:
104 | 0: name: "punctuation.terminator.statement.ditroff.desc"
105 | patterns: [
106 | name: "constant.numeric.range.ditroff.desc"
107 | match: "\\d+(-)\\d+"
108 | captures: 1: name: "punctuation.separator.range.dash.ditroff.desc"
109 |
110 | {include: "#comment"}
111 | {name: "constant.numeric.integer.desc", match: "\\d+"}
112 | {name: "variable.parameter.ditroff.desc", match: "\\S{2,}"}
113 | ]
114 | },{
115 | name: "meta.papersize.ditroff.desc"
116 | begin: "^\\s*(papersize)(?=\\s)"
117 | end: "(?=$|#)"
118 | beginCaptures:
119 | 1: name: "entity.type.var.ditroff.desc"
120 | patterns: [include: "#paperSizes"]
121 | },{
122 | begin: """(?x)^\\s*
123 | (afmfonts|allpunct|anysize|biggestfont|broken|checksum|designsize|encoding|family
124 | |fonts|hor|image_generator|internalname|lc_ctype|name|orientation|paper(?:length|width)
125 | |pass_filenames|postpro|prepro|print|res|sizescale|slant|spacewidth|spare\\d|special
126 | |styles|tcommand|unicode|unitwidth|unscaled_charwidths|use_charnames_in_special|vert
127 | |X11|(?:lbp|pcl)[a-z]+)
128 | (?=\\s)"""
129 | end: "(?=$|#)"
130 | beginCaptures:
131 | 1: name: "entity.type.var.ditroff.desc"
132 | patterns: [
133 | {name: "constant.numeric.ditroff.desc", match: "-?[\\d.]+(?=\\s|$)"}
134 | {name: "variable.parameter.ditroff.desc", match: "\\S+"}
135 | ]
136 | }]
137 |
138 |
139 | fontPath:
140 | match: "^(?:(\\w+)?\\t+)?(\\S+)\\t+(\\*)?(\\S+(?:\\.pf[ab]|[\\/]Resource[\\/]Font[\\/]\\S+))\\s*$"
141 | captures:
142 | 1: name: "variable.other.foundry.ditroff.desc"
143 | 2: name: "entity.name.var.ditroff.desc"
144 | 3: name: "keyword.operator.globstar.ditroff.desc"
145 | 4: name: "string.quoted.double.filename.ditroff.desc"
146 |
147 |
148 | foundry:
149 | name: "meta.foundry-data.ditroff.desc"
150 | begin: "^(#)Foundry\\|Name\\|Searchpath\\s*$"
151 | end: "(?=A)B"
152 | beginCaptures:
153 | 0: name: "comment.line.number-sign.ditroff.desc"
154 | 1: name: "punctuation.definition.comment.ditroff.desc"
155 | patterns: [include: "#comment", {
156 | match: "^([^\\s|]+)(\\|)([YN])(\\|)([rins]+)?(\\|)(?:([.\\w]*)(\\|)([.\\w]*)(?=\\|))?"
157 | captures:
158 | 1: name: "entity.name.var.ditroff.desc"
159 | 2: name: "punctuation.delimiter.pipe.ditroff.desc"
160 | 3: name: "constant.boolean.is-base64.ditroff.desc"
161 | 4: name: "punctuation.delimiter.pipe.ditroff.desc"
162 | 5: name: "constant.language.flags.ditroff.desc"
163 | 6: name: "punctuation.delimiter.pipe.ditroff.desc"
164 | 7: name: "variable.parameter.ditroff.desc"
165 | 8: name: "punctuation.delimiter.pipe.ditroff.desc"
166 | 9: name: "variable.parameter.ditroff.desc"
167 | },{
168 | match: "^(foundry)(\\|)(\\w*)(\\|)((\\()\\w+(\\)))?([^|#]+)"
169 | captures:
170 | 1: name: "storage.type.foundry.ditroff.desc"
171 | 2: name: "punctuation.delimiter.pipe.ditroff.desc"
172 | 3: name: "variable.other.foundry.ditroff.desc"
173 | 4: name: "punctuation.delimiter.pipe.ditroff.desc"
174 | 5: name: "string.interpolated.ditroff.desc"
175 | 6: name: "punctuation.definition.arguments.begin.ditroff.desc"
176 | 7: name: "punctuation.definition.arguments.end.ditroff.desc"
177 | 8: name: "string.quoted.double.filename.ditroff.desc", patterns: [
178 | match: ":", name: "punctuation.separator.key-value.colon.ditroff.desc"
179 | ]
180 | },{
181 | name: "meta.foundry-font.ditroff.desc"
182 | match: "(?<=\\|)(?:([^|!]+\\.pf[ab])|([^|!]+)(!)([^|!]+\\.pf[ab]))$"
183 | captures:
184 | 1: name: "string.quoted.double.filename.ditroff.desc"
185 | 2: name: "variable.parameter.ditroff.desc"
186 | 3: name: "punctuation.separator.fontname.ditroff.desc"
187 | 4: name: "string.quoted.double.filename.ditroff.desc"
188 | },{
189 | name: "meta.afmtodit-flag.ditroff.desc"
190 | match: "^([a-z])(=)(?=-)([^#]+)(?=$|#)"
191 | captures:
192 | 1: name: "variable.other.ditroff.desc"
193 | 2: name: "keyword.operator.assignment.ditroff.desc"
194 | 3: name: "constant.other.ditroff.desc"
195 | }, match: "\\|", name: "punctuation.delimiter.pipe.ditroff.desc"]
196 |
197 | kernpairs:
198 | name: "meta.kernpairs.ditroff.desc"
199 | begin: "^(kernpairs)\\s*$"
200 | end: "^(?=charset|\\s*$)"
201 | beginCaptures:
202 | 1: name: "keyword.control.section.ditroff.desc"
203 | patterns: [{
204 | name: "meta.kerning-pair.ditroff.desc"
205 | match: "^\\s*(\\S+)\\s+(\\S+)\\s+(-?\\d+)"
206 | captures:
207 | 1: name: "entity.name.var.ditroff.desc"
208 | 2: name: "entity.name.var.ditroff.desc"
209 | 3: name: "constant.numeric.integer.ditroff.desc"
210 | }]
211 |
212 |
213 | paperSizes:
214 | patterns: [{
215 | name: "support.constant.papersize.ditroff.desc"
216 | match: "(?i)(?:[A-D][0-7]|DL|letter|legal|tabloid|ledger|statement|executive|com10|monarch)(?=$|[\\s#])"
217 | },{
218 | name: "meta.custom-papersize.ditroff.desc"
219 | match: "(?<=\\s)([\\d.]+)([icpP])(,)([\\d.]+)([icpP])(?=\\s|$)"
220 | captures:
221 | 1: name: "constant.numeric.ditroff.desc"
222 | 2: name: "keyword.other.unit.ditroff.desc"
223 | 3: name: "punctuation.delimiter.comma.ditroff.desc"
224 | 4: name: "constant.numeric.ditroff.desc"
225 | 5: name: "keyword.other.unit.ditroff.desc"
226 | }]
227 |
--------------------------------------------------------------------------------
/grammars/gremlin.cson:
--------------------------------------------------------------------------------
1 | name: "Gremlin Image"
2 | scopeName: "source.gremlin"
3 | fileTypes: [
4 | "gsrc"
5 | "grn"
6 | "gremlin"
7 | ]
8 | firstLineMatch: "^\\s*(?:sun)?gremlinfile(?=\\s|$)"
9 | patterns: [
10 | {include: "#data"}
11 | {include: "#tags"}
12 | ]
13 |
14 | repository:
15 |
16 | # .GS/.GE: Tags for embedding gremlin pictures
17 | tags:
18 | begin: "^([.'])[ \\t]*(GS)(?=$|\\s|\\\\E?[\"#])(.*)$"
19 | end: "^([.'])[ \\t]*(GE|GF)(?=$|\\s|\\\\E?[\"#])"
20 | beginCaptures:
21 | 0: name: "meta.function.begin.gremlin.macro.roff"
22 | 1: name: "punctuation.definition.macro.roff"
23 | 2: name: "entity.function.name.roff"
24 | 3: patterns: [{
25 | name: "constant.language.alignment-mode.grn.roff"
26 | match: "(?:^|\\G)\\s*([LIC])\\b"
27 | }, include: "text.roff#escapes"]
28 | endCaptures:
29 | 0: name: "meta.function.end.gremlin.macro.roff"
30 | 1: name: "punctuation.definition.macro.roff"
31 | 2: name: "entity.name.function.roff"
32 | patterns: [{
33 | begin: "\\A\\s*((?:sun)?gremlinfile)(?=\\s|$)"
34 | end: "(?=A)B"
35 | patterns: [include: "#data"]
36 | }, include: "#grn"]
37 |
38 |
39 | # grn(1) preprocessor commands
40 | grn:
41 | patterns: [{
42 | # Boolean-type parameter
43 | name: "meta.directive.preprocessor.grn.roff"
44 | match: """(?ix) ^\\s*
45 | (pointscale|pointscal|pointsca|pointsc|points
46 | |point|poin|poi|po|p) (?:\\s+(on|off))?
47 | (?=\\s|$) """
48 | captures:
49 | 1: name: "keyword.operator.point-scale.grn.roff"
50 | 2: name: "constant.language.boolean.grn.roff"
51 | },{
52 | # Load gremlin data from file "XX"
53 | name: "meta.directive.preprocessor.grn.roff"
54 | match: "(?i)^\\s*(file|fil|fi|f)\\s+(\\S.*)"
55 | captures:
56 | 1: name: "keyword.control.directive.include.grn.roff"
57 | 2: name: "string.unquoted.filename.grn.roff"
58 | },{
59 | # Everything else expected between .GS/.GE tags
60 | name: "keyword.operator.directive.preprocessor.grn.roff"
61 | match: """(?ix) ^\\s*
62 | ( [1-4] (?=\\s+\\S)
63 | | roman|roma|rom|ro|r
64 | | italics|italic|itali|ital|ita|it|i
65 | | bold|bol|bo|b
66 | | special|specia|speci|spec|spe|sp
67 | | stipple|stippl|stipp|stip|sti|st|l
68 | | scale|scal|sca|sc|x
69 | | narrow|narro|narr|nar|na
70 | | medium|mediu|medi|med|me
71 | | thick|thic|thi|th|t
72 | | default|defaul|defau|defa|def|de|d
73 | | width|widt|wid|wi|w
74 | | height|heigh|heig|hei|he|h
75 | ) (?=\\s|$)"""
76 | }, include: "text.roff#params"]
77 |
78 |
79 | # Actual image coordinates
80 | data:
81 | begin: "^\\s*((?:sun)?gremlinfile)(?=\\s|$).*"
82 | end: "^\\s*(-1)\\s*$"
83 | beginCaptures:
84 | 0: name: "meta.file.start.gremlin"
85 | 1: name: "keyword.control.flow.begin-file.gremlin"
86 | endCaptures:
87 | 0: name: "meta.file.end.gremlin"
88 | 1: name: "comment.line.ignored.end-of-file.gremlin"
89 | contentName: "meta.file.body.gremlin"
90 | patterns: [{
91 | name: "keyword.operator.element-specification.sun.gremlin"
92 | match: """(?x)\\b
93 | (ARC|BEZIER|BOTCENT|BOTLEFT|BOTRIGHT|BSPLINE|CENTCENT|CENTLEFT
94 | |CENTRIGHT|CURVE|POLYGON|TOPCENT|TOPLEFT|TOPRIGHT|VECTOR)\\b"""
95 | },{
96 | match: "^\\s*([0-6])\\s+([0-9]+)\\s*$"
97 | captures:
98 | 1: name: "keyword.operator.element-brush.gremlin"
99 | 2: name: "keyword.operator.element-size.gremlin"
100 | },{
101 | name: "comment.line.ignored.end-point-list.gremlin"
102 | match: "^\\s*(?:\\*|-1.0+\\s+-1.0+)\\s*$"
103 | },{
104 | name: "keyword.operator.element-specification.aed.gremlin"
105 | match: "^\\s*[0-9]+(?:\\s*$|\\s+(?=\\d))"
106 | },{
107 | match: """(?x) ^\\s*
108 | ( (6) \\s+ (\\S.{5})
109 | | (5) \\s+ (\\S.{4})
110 | | (4) \\s+ (\\S.{3})
111 | | (3) \\s+ (\\S.{2})
112 | | (2) \\s+ (\\S.{1})
113 | | (1) \\s+ ([7-9\\D])
114 | | ((?!0)\\d+) \\s+ (\\S.*)
115 | ) \\s* $"""
116 | captures:
117 | 1: name: "meta.element-text.gremlin"
118 | 2: name: "keyword.operator.character-count.6.gremlin"
119 | 3: name: "string.unquoted.gremlin"
120 | 4: name: "keyword.operator.character-count.5.gremlin"
121 | 5: name: "string.unquoted.gremlin"
122 | 6: name: "keyword.operator.character-count.4.gremlin"
123 | 7: name: "string.unquoted.gremlin"
124 | 8: name: "keyword.operator.character-count.3.gremlin"
125 | 9: name: "string.unquoted.gremlin"
126 | 10: name: "keyword.operator.character-count.2.gremlin"
127 | 11: name: "string.unquoted.gremlin"
128 | 12: name: "keyword.operator.character-count.1.gremlin"
129 | 13: name: "string.unquoted.gremlin"
130 | 14: name: "keyword.operator.character-count.gremlin"
131 | 15: name: "string.unquoted.gremlin"
132 | },{
133 | name: "constant.numeric.decimal.gremlin"
134 | match: "\\d+(?:\\.\\d+)?"
135 | }]
136 |
137 |
--------------------------------------------------------------------------------
/grammars/ideal.cson:
--------------------------------------------------------------------------------
1 | # IDEAL picture preprocessor
2 | scopeName: "source.ideal"
3 | patterns: [
4 | {include: "#external"}
5 | {include: "#main"}
6 | ]
7 |
8 | repository:
9 |
10 | # Sensible handling of embedded processor source
11 | external:
12 | patterns: [include: "source.pic#tags", {
13 |
14 | # Roff lines
15 | begin: "^(?=[.'][ \\t]*(?:\\w|\\\\))"
16 | end: "(?"
157 | beginCaptures: 0: name: "punctuation.definition.bracket.angle.ideal"
158 | endCaptures: 0: name: "punctuation.definition.bracket.angle.ideal"
159 | }]
160 |
161 |
162 | strings:
163 | patterns: [{
164 |
165 | # 'Single-quoted string'
166 | name: "string.quoted.single.ideal"
167 | begin: "'"
168 | end: "'"
169 | beginCaptures: 0: name: "punctuation.definition.string.begin.ideal"
170 | endCaptures: 0: name: "punctuation.definition.string.end.ideal"
171 |
172 | }, {
173 | # "Double-quoted string"
174 | name: "string.quoted.double.ideal"
175 | begin: '"'
176 | end: '"'
177 | beginCaptures: 0: name: "punctuation.definition.string.begin.ideal"
178 | endCaptures: 0: name: "punctuation.definition.string.end.ideal"
179 | patterns: [include: "#escapes"]
180 | }]
181 |
182 |
183 | # Local variables
184 | variables:
185 | begin: "\\b(var)\\b"
186 | end: "(?=;)"
187 | patterns: [include: "#punctuation"]
188 | beginCaptures:
189 | 1: name: "storage.type.var.ideal"
190 |
--------------------------------------------------------------------------------
/grammars/manref.cson:
--------------------------------------------------------------------------------
1 | # Injected links to man(1) pages
2 | scopeName: "hidden.manref"
3 | injectionSelector: "text - (text.info | text.texinfo), comment, docstring, string.quoted.docstring, source.changelogs"
4 | patterns: [{
5 | name: "manref"
6 | match: """(?xi)
7 | # Subject
8 | ((?:
9 | [^:\\s()<>/\"'`{}!&*\\#?\\\\]
10 | |
11 | # Avoid matching scheme component of “man:man(1)” URLs
12 | (?-i: (? {
17 | for(const file of packageFiles)
18 | disposables.add(...require(file)());
19 | });
20 | },
21 |
22 | deactivate(){
23 | if(null !== disposables){
24 | disposables.dispose();
25 | disposables = null;
26 | }
27 | },
28 |
29 | createTroffView(state){
30 | const fs = require("fs");
31 | if(state.editorId || fs.existsSync(state.filePath) && fs.lstatSync(state.filePath).isFile()){
32 | const TroffView = require("./lib/views/troff-view.js");
33 | return new TroffView(state);
34 | }
35 | },
36 | };
37 |
--------------------------------------------------------------------------------
/keymaps/keys.cson:
--------------------------------------------------------------------------------
1 | "atom-text-editor[data-grammar='text roff']":
2 | "tab": "language-roff:indent"
3 | "shift-tab": "language-roff:outdent"
4 | "{": "language-roff:curly-bracket"
5 | "(": "language-roff:round-bracket"
6 | "'": "language-roff:single-quote"
7 |
8 | ".platform-darwin atom-text-editor[data-grammar='text roff']":
9 | "cmd-e": "language-roff:eval-selection"
10 |
11 | ".platform-win32 atom-text-editor[data-grammar='text roff'],
12 | .platform-linux atom-text-editor[data-grammar='text roff']":
13 | "ctrl-e": "language-roff:eval-selection"
14 |
15 | ".platform-darwin,
16 | .platform-darwin atom-text-editor[class]":
17 | "cmd-m": "language-roff:open-manpage"
18 |
19 | ".platform-win32, .platform-win32 atom-text-editor[class],
20 | .platform-linux, .platform-linux atom-text-editor[class]":
21 | "ctrl-m": "language-roff:open-manpage"
22 |
23 | ".platform-darwin .troff-view":
24 | "cmd-a": "language-roff:select-all"
25 |
26 | ".platform-win32 .troff-view,
27 | .platform-linux .troff-view":
28 | "ctrl-a": "language-roff:select-all"
29 |
--------------------------------------------------------------------------------
/lib/conversion.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const {existsSync} = require("fs");
4 | const {join, parse} = require("path");
5 | const {getGroffAdapter} = require("./utils.js");
6 |
7 | // Preload adapter at startup to improve response time
8 | require("roff").GroffAdapter.loadDefault();
9 |
10 |
11 | /**
12 | * Register commands for processing/converting Roff documents.
13 | * @return {Disposable[]}
14 | * @internal
15 | */
16 | module.exports = () => [
17 | atom.commands.add(buildSelector(), {
18 | "language-roff:save-as-dvi": ev => save(ev, "dvi"),
19 | "language-roff:save-as-html": ev => save(ev, "html"),
20 | "language-roff:save-as-pdf": ev => save(ev, "pdf"),
21 | "language-roff:save-as-ps": ev => save(ev, "ps"),
22 | "language-roff:save-as-ascii": ev => save(ev, "ascii"),
23 | "language-roff:save-as-utf8": ev => save(ev, "utf8"),
24 | "language-roff:save-as-dvi-landscape": ev => save(ev, "dvi", {landscape: true}),
25 | "language-roff:save-as-pdf-landscape": ev => save(ev, "pdf", {landscape: true}),
26 | "language-roff:save-as-ps-landscape": ev => save(ev, "ps", {landscape: true}),
27 | }),
28 | ];
29 |
30 |
31 | /**
32 | * Construct the selector string for scoping conversion commands.
33 | * @return {String}
34 | * @private
35 | */
36 | function buildSelector(){
37 | return "atom-text-editor[data-grammar='text roff']"
38 | + ", atom-text-editor[data-grammar='source pic']"
39 | + ", .tree-view .file > .manpage-icon[data-path], .tab > .manpage-icon[data-path]"
40 | + ("1 2 3 4 5 6 7 8 9 chem man mdoc me ms n pic roff tr".split(/\s/)
41 | .map(ext => `, .tree-view .file > [data-path$=".${ext}"], .tab > [data-path$=".${ext}"]`)
42 | .join("\n"));
43 | }
44 |
45 |
46 | /**
47 | * Wrapper function which handles a specific "Save as…" command.
48 | *
49 | * @param {CustomEvent} event - Object describing the triggered event
50 | * @param {String} format - Lowercased name of format being saved to
51 | * @param {Object} [options] - Parameters passed to {@link GroffAdapter#format}
52 | * @private
53 | */
54 | async function save(event, format, options = {}){
55 | let editor = null;
56 | options = {auto: true, ...options};
57 |
58 | const groff = await getGroffAdapter();
59 | if(!groff) return;
60 |
61 | // macOS ships with Groff 1.19.2, which lacks a PDF driver
62 | if("pdf" === format && !groff.devices.pdf){
63 | const text = "PDF output not supported by Groff installation";
64 | let description = "Please upgrade Groff to the newest version, then restart Atom.";
65 | if("darwin" === process.platform) description += [,
66 | "You can install the latest Groff using [Homebrew](https://brew.sh/):",
67 | "~~~\nbrew install groff\n~~~",
68 | ].join("\n\n");
69 | const note = atom.notifications.addError(text, {description, dismissable: true});
70 | console.log(note);
71 | return;
72 | }
73 |
74 | if(event.target.matches("atom-text-editor, atom-text-editor *")){
75 | editor = atom.workspace.getActiveTextEditor();
76 | options.inputFile = editor.getPath();
77 | }
78 | else options.inputFile = event.target.dataset.path;
79 |
80 | const ext = ("utf8" === format || "ascii" === format) ? "txt" : format;
81 | options.outputFile = getSaveLocation(options.inputFile, ext);
82 | if(!options.outputFile) return;
83 |
84 | let inputData = null;
85 | if(editor && !(options.inputFile && !editor.isModified() && existsSync(options.inputFile)))
86 | inputData = editor.getText();
87 |
88 | try{
89 | await groff.format(inputData, format, options);
90 | const text = "Saved to `" + options.outputFile + "`";
91 | const note = atom.notifications.addSuccess(text, {dismissable: true});
92 | setTimeout(() => note.dismiss(), 2000);
93 | }
94 | catch(error){
95 | atom.notifications.addError(error.message, {
96 | detail: error.stack,
97 | dismissable: true,
98 | });
99 | }
100 | }
101 |
102 |
103 | /**
104 | * Prompt the user for a system path to save to.
105 | *
106 | * @param {String} path
107 | * @param {String} format
108 | * @return {String}
109 | */
110 | function getSaveLocation(path, format){
111 | if(path){
112 | const {dir, name} = parse(path);
113 | path = join(dir, `${name}.${format}`);
114 | }
115 | else if(path = atom.project.getPaths()[0])
116 | path = join(path, "Untitled." + format);
117 |
118 | return atom.applicationDelegate.showSaveDialog({defaultPath: path});
119 | }
120 |
--------------------------------------------------------------------------------
/lib/hyperlinks.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const {Disposable} = require("atom");
4 | const OPTION_NAME = "language-roff.linkManPageReferences";
5 |
6 |
7 | module.exports = () => [
8 | attachClickHandler(),
9 | atom.config.observe(OPTION_NAME, value =>
10 | document.body.classList.toggle("disable-manref-links", !value)),
11 | ];
12 |
13 |
14 | /**
15 | * Attach a listener to intercept `click` events.
16 | *
17 | * @param {String} [eventType="pointerdown"]
18 | * @return {Disposable} Removes listener when disposed.
19 | */
20 | function attachClickHandler(eventType = "pointerdown"){
21 | document.body.removeEventListener(eventType, handleClick);
22 | document.body.addEventListener(eventType, handleClick);
23 | return new Disposable(() =>
24 | document.body.removeEventListener(eventType, handleClick));
25 | }
26 |
27 |
28 | /**
29 | * Intercept a `click` event on a tokenised man-page reference.
30 | *
31 | * @param {MouseEvent|PointerEvent} event
32 | * @this {HTMLBodyElement}
33 | * @internal
34 | */
35 | function handleClick(event){
36 | let {target} = event;
37 | if(event.metaKey || event.ctrlKey || event.button > 0) return; // Respond to left-clicks only
38 | if(!atom.config.get(OPTION_NAME)) return; // User's disabled man-links
39 | if("object" !== typeof target || !target.classList) return; // Shouldn't happen
40 |
41 | if(target.classList.contains("syntax--manref") && target.closest("atom-text-editor")){
42 | event.preventDefault();
43 | event.stopImmediatePropagation();
44 | if("syntax--manref" !== target.className)
45 | target = target.closest("atom-text-editor [class='syntax--manref']");
46 | if(target){
47 | const subject = (target.querySelector(".syntax--subject") || {}).textContent || "";
48 | const section = (target.querySelector(".syntax--section") || {}).textContent || "";
49 | const pageURL = `man://${subject}/${section.replace(/^\(|\)$/g, "")}`;
50 | atom.workspace.open(pageURL, {split: "right"});
51 | }
52 | return false;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/previewing.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const {getGroffAdapter, getManPrompt, getManAdapter} = require("./utils.js");
4 | const {parseManURL, TTYRenderer} = require("roff");
5 | let evalAdapter = null;
6 |
7 |
8 | module.exports = () => [
9 | atom.workspace.addOpener(uri => {
10 | const page = parseManURL(uri);
11 | if(null !== page)
12 | return new (require("./views/troff-view.js"))(page);
13 | }),
14 |
15 | atom.commands.add("body", {
16 | "language-roff:eval-selection": () => evalSelection(),
17 | "language-roff:open-manpage": () => runOpenCmd(),
18 | "language-roff:clear-cached-data": () => runClearCmd(),
19 | }),
20 | ];
21 |
22 |
23 | /**
24 | * Pipe the current selection through `groff -Tutf8` and display the output.
25 | * @internal
26 | */
27 | async function evalSelection(){
28 | const editor = atom.workspace.getActiveTextEditor();
29 | const ranges = editor.getSelectionsOrderedByBufferPosition();
30 | const source = ranges.map(range => range.getText()).join("") || editor.getText();
31 | if(!source) return;
32 | evalAdapter = evalAdapter || new TTYRenderer();
33 | const output = await (await getGroffAdapter()).format(source, "utf8", {raw: true});
34 | let popup = atom.notifications.addInfo("Output of `groff -Tutf8`", {
35 | description: "```\n```\n",
36 | dismissable: true,
37 | });
38 | popup = atom.views.getView(popup).element;
39 | const el = popup.querySelector("pre");
40 | el.innerHTML = evalAdapter.process(output).trim();
41 | el.className = "tty-output";
42 | }
43 |
44 |
45 | /**
46 | * Display a notification indicating no entry was found for a queried topic.
47 | *
48 | * @param {String} name - Topic name
49 | * @param {String} [section=""] - Optional section name
50 | * @return {Notification}
51 | * @internal
52 | */
53 | function notifyNothingMatched(name, section = ""){
54 | const message = section
55 | ? `No manual entry found for \`${name}\` in section \`${section}\`.`
56 | : `No manual entry found for \`${name}\`.`;
57 | const options = {dismissable: true};
58 | options.description = "This lookup may have been cached from an earlier call."
59 | + " If you believe this to be an error, clear the cache using the"
60 | + " `language-roff:clear-cached-data` command, then try again.";
61 | return atom.notifications.addError(message, options);
62 | }
63 |
64 |
65 | /**
66 | * Handler for the "open manual-page" command.
67 | * @return {Notification}
68 | * @internal
69 | */
70 | async function runOpenCmd(){
71 | let input = await getManPrompt().promptUser({
72 | headerText: "Enter the name of a manual-page",
73 | footerHTML: "E.g., perl
, 5 grep
, grep(5)
",
74 | });
75 |
76 | if(input && (input = input.split(/\s+/).filter(Boolean))[0]){
77 | const man = await getManAdapter();
78 | const paths = await man.find(...input);
79 | paths[0]
80 | ? atom.workspace.open(`man://${input.join("/")}`)
81 | : notifyNothingMatched(...input);
82 | }
83 | }
84 |
85 |
86 | /**
87 | * Flush all cached lookups and loaded file data.
88 | * @internal
89 | */
90 | async function runClearCmd(){
91 | const man = await getManAdapter();
92 | const {size} = man.cache;
93 | man.cache.clear();
94 | const plural = size !== 1 ? "s" : "";
95 | const message = `Cleared ${size} object${plural} from cache.`;
96 | atom.notifications.addInfo(message, {dismissable: true});
97 | }
98 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const {Disposable} = require("atom");
4 | let promptView = null;
5 | let manAdapter = null;
6 | let groffAdapter = null;
7 |
8 |
9 | module.exports = {
10 |
11 | /**
12 | * Attach an external stylesheet to the workspace.
13 | *
14 | * @param {String} href - Path to CSS file
15 | * @param {Element} [parentElement] - DOM node to add to
16 | * @return {Disposable} Removes stylesheet from DOM when disposed
17 | */
18 | attachStyleSheet(href, parentElement = null){
19 | const link = document.createElement("link");
20 | link.href = href;
21 | link.rel = "stylesheet";
22 | link.type = "text/css";
23 |
24 | (parentElement = parentElement || document.querySelector("atom-styles"))
25 | ? parentElement.insertBefore(link, parentElement.firstChild)
26 | : document.head.appendChild(link);
27 |
28 | return new Disposable(() => {
29 | if({parentElement} = link)
30 | parentElement.removeChild(link);
31 | });
32 | },
33 |
34 |
35 | /**
36 | * Stop a function from firing too quickly.
37 | *
38 | * Returns a copy of the original function that runs only after the designated
39 | * number of milliseconds have elapsed. Useful for throttling onResize handlers.
40 | *
41 | * @param {Function} fn - Function to debounce
42 | * @param {Number} [limit=0] - Threshold to stall execution by, in milliseconds.
43 | * @param {Boolean} [asap=false] - Call function *before* threshold elapses, not after.
44 | * @return {Function}
45 | */
46 | debounce(fn, limit = 0, asap = false){
47 | let started, context, args, timing;
48 |
49 | function delayed(){
50 | const timeSince = Date.now() - started;
51 | if(timeSince >= limit){
52 | if(!asap) fn.apply(context, args);
53 | if(timing) clearTimeout(timing);
54 | timing = context = args = null;
55 | }
56 | else timing = setTimeout(delayed, limit - timeSince);
57 | }
58 |
59 | // Debounced copy of original function
60 | return function(...args){
61 | context = this;
62 | if(!limit)
63 | return fn.apply(context, args);
64 | started = Date.now();
65 | if(!timing){
66 | if(asap) fn.apply(context, args);
67 | timing = setTimeout(delayed, limit);
68 | }
69 | };
70 | },
71 |
72 |
73 | /**
74 | * Retrieve adapter instance for integrating with man(1).
75 | *
76 | * @return {ManAdapter}
77 | * @internal
78 | */
79 | async getManAdapter(){
80 | if(null === manAdapter){
81 | const {ManAdapter} = require("roff");
82 | manAdapter = await ManAdapter.loadDefault();
83 | }
84 | return manAdapter;
85 | },
86 |
87 |
88 | /**
89 | * Retrieve adapter instance for integrating with groff(1).
90 | *
91 | * @return {GroffAdapter}
92 | * @internal
93 | */
94 | async getGroffAdapter(){
95 | if(null === groffAdapter){
96 | const {GroffAdapter} = require("roff");
97 | try{ groffAdapter = await GroffAdapter.loadDefault(); }
98 | catch(error){
99 | const text = "Unable to initialise Groff adapter.";
100 | console.error(error);
101 |
102 | // Assume Windows users haven't installed Groff yet
103 | if("win32" === process.platform){
104 | const url = "https://www.gnu.org/software/groff/#downloading";
105 | error = "[Download](" + url + ") and install Groff, then restart Atom.";
106 | }
107 | atom.notifications.addError(text, {description: error, dismissable: true});
108 | }
109 | }
110 | return groffAdapter;
111 | },
112 |
113 |
114 | /**
115 | * Retrieve view for querying users for input for man(1)-related commands.
116 | *
117 | * @return {PromptView}
118 | * @internal
119 | */
120 | getManPrompt(){
121 | if(null === promptView){
122 | const PromptView = require("prompt-view");
123 | promptView = new PromptView({
124 | headerTagName: "label",
125 | headerClass: "prompt-header manpage-icon icon icon-book",
126 | footerClass: "prompt-footer message",
127 | });
128 | }
129 | return promptView;
130 | },
131 | };
132 |
--------------------------------------------------------------------------------
/lib/views/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %TITLE%
6 |
20 |
21 | %CONTENT%
22 |
23 |
--------------------------------------------------------------------------------
/lib/views/troff-view.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const {join, parse} = require("path");
4 | const {readFileSync, writeFileSync} = require("fs");
5 | const {TTYRenderer, parseManURL} = require("roff");
6 | const {debounce, getManAdapter, getGroffAdapter} = require("../utils.js");
7 | const {CompositeDisposable, Disposable, Emitter} = require("atom");
8 |
9 |
10 | /**
11 | * Class which displays rendered previews of roff(7) documents.
12 | * @internal
13 | * @class
14 | */
15 | class TroffView{
16 |
17 | constructor(page = {}){
18 | this.renderer = new TTYRenderer();
19 | this.disposables = new CompositeDisposable();
20 | this.emitter = new Emitter();
21 |
22 | // Pane-item element (outermost container)
23 | this.element = document.createElement("div");
24 | this.element.className = "troff-view native-key-bindings";
25 | this.element.tabIndex = -1;
26 | this.element.addEventListener("click", event => {
27 | const selector = "a[href^='man:']";
28 | const link = event.target.matches(selector) ? event.target : event.target.closest(selector);
29 | if(link){
30 | const page = parseManURL(link.href);
31 | this.loadPage(page.name, page.section);
32 | event.preventDefault();
33 | event.stopImmediatePropagation();
34 | }
35 | });
36 |
37 | // Element directly inside this.element which receives rendered HTML
38 | this.content = this.element.appendChild(document.createElement("pre"));
39 | this.disposables.add(
40 | atom.commands.add(this.element, {
41 | "language-roff:select-all": this.selectAll.bind(this),
42 | "language-roff:edit-source": this.openInEditor.bind(this),
43 | "language-roff:save-as-html": this.saveAsHTML.bind(this),
44 | "language-roff:save-as-utf8": this.saveAsText.bind(this),
45 | }),
46 | atom.config.observe("editor.lineHeight", value => this.content.style.lineHeight = value),
47 | atom.config.observe("editor.fontFamily", value => this.content.style.fontFamily = value),
48 | atom.config.observe("editor.fontSize", value => {
49 | this.content.style.fontSize = value + "px";
50 | this.render();
51 | })
52 | );
53 |
54 | // Re-render content when element is resized
55 | const onResize = debounce(this.render.bind(this));
56 | window.addEventListener("resize", onResize);
57 | this.disposables.add(
58 | new Disposable(() => window.removeEventListener("resize", onResize)),
59 | atom.workspace.observeActivePaneItem(onResize),
60 | atom.workspace.observePanes(pane => pane.onDidChangeFlexScale(onResize))
61 | );
62 |
63 | this.name = page.name || "";
64 | this.section = page.section || "";
65 | this.loadPage(page.name, page.section);
66 | }
67 |
68 |
69 | static deserialize(attr){
70 | return new TroffView(attr);
71 | }
72 |
73 | serialize(){
74 | return {
75 | deserializer: "TroffView",
76 | filePath: this.path,
77 | editorId: this.editorId,
78 | name: this.name,
79 | section: this.section,
80 | title: this.title,
81 | };
82 | }
83 |
84 |
85 | destroy(){
86 | this.emitter.emit("did-destroy");
87 | this.emitter.dispose();
88 | this.disposables.dispose();
89 | this.element.remove();
90 | }
91 |
92 |
93 | onDidChangeTitle(fn){
94 | return this.emitter.on("did-change-title", fn);
95 | }
96 |
97 |
98 | getIconName(){
99 | return "manpage";
100 | }
101 |
102 |
103 | getURI(){
104 | const frag = this.fragment ? "#" + this.fragment : "";
105 | return `man://${this.name}/${this.section}` + frag;
106 | }
107 |
108 |
109 | getTitle(){
110 | if(!this.name) return "Untitled";
111 | let title = this.name;
112 | if(this.section) title += `(${this.section})`;
113 | return title;
114 | }
115 |
116 |
117 | getPath(){
118 | return this.path || "";
119 | }
120 |
121 |
122 | get isVisible(){
123 | const pane = atom.workspace.paneForItem(this);
124 | return pane && pane.activeItem === this;
125 | }
126 |
127 |
128 | /**
129 | * Current width of the viewer's element, measured in columns.
130 | * @property {Number} width
131 | * @readonly
132 | */
133 | get width(){
134 | return Math.round(this.content.offsetWidth / atom.config.get("editor.fontSize") - 1);
135 | }
136 |
137 |
138 | async loadPage(name, section){
139 | if(!name) return;
140 | const man = await getManAdapter();
141 | const paths = await man.find(name, section);
142 |
143 | // Show an error if page couldn't be loaded
144 | if(!paths.length || !paths[0])
145 | return atom.notifications.addError(section
146 | ? `No manual entry for ${name} in section ${section}`
147 | : `No manual entry for ${name}`);
148 | else{
149 | this.path = paths[0];
150 | this.name = name;
151 | this.section = section || this.sectionByFilename(this.path);
152 | this.emitter.emit("did-change-title");
153 | this.source = await man.load(this.path);
154 | return this.render();
155 | }
156 | }
157 |
158 |
159 | async render(){
160 | if(!this.source || !this.isVisible) return;
161 |
162 | // Nothing's changed that warrants re-rendering
163 | const {lastRender, width, path} = this;
164 | if(lastRender && width === lastRender.width && path === lastRender.path)
165 | return;
166 |
167 | const groff = await getGroffAdapter();
168 | const source = this.renderer.process(await groff.format(this.source, "utf8", {
169 | pageWidth: width + "v",
170 | auto: true,
171 | raw: true,
172 | }));
173 | if(source !== this.content.innerHTML){
174 | this.content.innerHTML = source;
175 | if(!lastRender || lastRender.path !== path)
176 | this.element.scrollTop = 0;
177 | this.lastRender = {path, width};
178 | }
179 | }
180 |
181 |
182 | async openInEditor(){
183 | if(!this.path) return;
184 | const container = atom.workspace.paneContainerForItem(this);
185 | const activePane = atom.workspace.paneForItem(this);
186 | const panes = container.getPanes().filter(pane => pane !== activePane);
187 | return atom.workspace.open(this.path, panes.length ? {pane: panes.shift()} : {});
188 | }
189 |
190 |
191 | /**
192 | * Save the rendered man-page as monospaced HTML.
193 | * @return {Promise}
194 | * @internal
195 | */
196 | async saveAsHTML(){
197 | const location = await this.getSaveLocation(".html");
198 | if(!location) return;
199 |
200 | const viewStyle = window.getComputedStyle(this.element);
201 | const pageStyle = window.getComputedStyle(this.content);
202 | const source = readFileSync(join(__dirname, "template.html"), "utf8")
203 | .replace(/%TITLE%/g, parse(location).name)
204 | .replace(/%BG%/g, viewStyle.backgroundColor)
205 | .replace(/%FG%/g, pageStyle.color)
206 | .replace(/%SIZE%/g, atom.config.get("editor.fontSize") + "px")
207 | .replace(/%LEADING%/g, atom.config.get("editor.lineHeight"))
208 | .replace(/%FAMILY%/g, atom.config.get("editor.fontFamily"))
209 | .replace(/%CONTENT%/g, this.content.innerHTML);
210 | return this.saveFile(location, source);
211 | }
212 |
213 |
214 | /**
215 | * Save the rendered man-page as UTF8-encoded plain text.
216 | * @return {Promise}
217 | * @internal
218 | */
219 | async saveAsText(){
220 | const location = await this.getSaveLocation(".txt");
221 | if(!location) return;
222 | return this.saveFile(location, this.content.textContent);
223 | }
224 |
225 |
226 | /**
227 | * Write a string to disk, and display a notification when successful.
228 | *
229 | * @param {String} location - Location to write to
230 | * @param {String} input - File content to be written
231 | * @return {Promise}
232 | * @internal
233 | */
234 | async saveFile(location, input){
235 | try{
236 | writeFileSync(location, input, "utf8");
237 | const text = "Saved to `" + location + "`";
238 | const note = atom.notifications.addSuccess(text, {dismissable: true});
239 | setTimeout(() => note.dismiss(), 2000);
240 | return note;
241 | }
242 | catch(error){
243 | return atom.notifications.addError(error.message, {
244 | detail: error.stack,
245 | dismissable: true,
246 | });
247 | }
248 | }
249 |
250 |
251 | /**
252 | * Prompt the user for a location to save to.
253 | *
254 | * @param {String} suffix - File extension with leading dot
255 | * @return {Promise}
256 | * @internal
257 | */
258 | async getSaveLocation(suffix = ""){
259 | const {name, section} = this;
260 | const filename = (name && section ? `${name}.${section}${suffix}` : name) || `Untitled${suffix}`;
261 | const defaultPath = join(atom.project.getPaths()[0] || require("os").homeDir(), filename);
262 | return atom.applicationDelegate.showSaveDialog({defaultPath});
263 | }
264 |
265 |
266 | /**
267 | * Extract the section number from a man-page's path.
268 | *
269 | * @param {String} input
270 | * @return {String}
271 | * @internal
272 | */
273 | sectionByFilename(input){
274 | input = input.replace(/\\/g, "/");
275 |
276 | // Extract from directory name if unambiguous
277 | if(/\/man\/man([0-9ln])\/[^/]+$/.test(input))
278 | return RegExp.lastParen;
279 |
280 | else(/\.([0-9][^.\s/]*|l|n)(?:\.[^0-9][^.\s/]*)?$/.test(input))
281 | ? RegExp.lastParen
282 | : "";
283 | }
284 |
285 |
286 | /**
287 | * Intercept `core:select-all` to stop user selecting the entire workspace.
288 | *
289 | * @param {CustomEvent} event
290 | * @return {Boolean} Returns false.
291 | * @internal
292 | */
293 | selectAll(event){
294 | event.preventDefault();
295 | event.stopPropagation();
296 | if(this.loading) return false;
297 |
298 | const selection = window.getSelection();
299 | selection.removeAllRanges();
300 | const range = document.createRange();
301 | range.selectNodeContents(this.element);
302 | selection.addRange(range);
303 | return false;
304 | }
305 | }
306 |
307 | module.exports = TroffView;
308 |
--------------------------------------------------------------------------------
/menus/roff.cson:
--------------------------------------------------------------------------------
1 | menu: [
2 | label: "Packages"
3 | submenu: [
4 | label: "Roff"
5 | submenu: [
6 | {label: "Open manual page", command: "language-roff:open-manpage"}
7 | {label: "Clear cached data", command: "language-roff:clear-cached-data"}
8 | ]
9 | ]
10 | ]
11 | "context-menu":
12 | ".troff-view": [
13 | {label: "Edit Source", command: "language-roff:edit-source"}
14 | {label: "Save as HTML\u2026", command: "language-roff:save-as-html"}
15 | {label: "Save as Plain Text\u2026", command: "language-roff:save-as-utf8"}
16 | {label: "Select All", command: "language-roff:select-all"}
17 | ]
18 | 'atom-text-editor[data-grammar="text roff"], atom-text-editor[data-grammar="source pic"],
19 | .tree-view .file > .manpage-icon[data-path], .tab > .manpage-icon[data-path],
20 | .tree-view .file > [data-path$=".1"], .tab > [data-path$=".1"],
21 | .tree-view .file > [data-path$=".2"], .tab > [data-path$=".2"],
22 | .tree-view .file > [data-path$=".3"], .tab > [data-path$=".3"],
23 | .tree-view .file > [data-path$=".4"], .tab > [data-path$=".4"],
24 | .tree-view .file > [data-path$=".5"], .tab > [data-path$=".5"],
25 | .tree-view .file > [data-path$=".6"], .tab > [data-path$=".6"],
26 | .tree-view .file > [data-path$=".7"], .tab > [data-path$=".7"],
27 | .tree-view .file > [data-path$=".8"], .tab > [data-path$=".8"],
28 | .tree-view .file > [data-path$=".9"], .tab > [data-path$=".9"],
29 | .tree-view .file > [data-path$=".chem"], .tab > [data-path$=".chem"],
30 | .tree-view .file > [data-path$=".man"], .tab > [data-path$=".man"],
31 | .tree-view .file > [data-path$=".mdoc"], .tab > [data-path$=".mdoc"],
32 | .tree-view .file > [data-path$=".me"], .tab > [data-path$=".me"],
33 | .tree-view .file > [data-path$=".ms"], .tab > [data-path$=".ms"],
34 | .tree-view .file > [data-path$=".n"], .tab > [data-path$=".n"],
35 | .tree-view .file > [data-path$=".pic"], .tab > [data-path$=".pic"],
36 | .tree-view .file > [data-path$=".roff"], .tab > [data-path$=".roff"],
37 | .tree-view .file > [data-path$=".tr"], .tab > [data-path$=".tr"]': [
38 | label: "Save as\u2026",
39 | submenu: [
40 | {label: "HTML", command: "language-roff:save-as-html"}
41 | {label: "PDF", command: "language-roff:save-as-pdf"}
42 | {label: "PDF, Landscape", command: "language-roff:save-as-pdf-landscape"}
43 | {label: "PostScript", command: "language-roff:save-as-ps"}
44 | {label: "PostScript, Landscape", command: "language-roff:save-as-ps-landscape"}
45 | {label: "TeX DVI", command: "language-roff:save-as-dvi"}
46 | {label: "TeX DVI, Landscape", command: "language-roff:save-as-dvi-landscape"}
47 | {label: "Plain text, ASCII", command: "language-roff:save-as-ascii"}
48 | {label: "Plain text, UTF-8", command: "language-roff:save-as-utf8"}
49 | ]
50 | ]
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "language-roff",
3 | "version": "1.3.1",
4 | "description": "Atom support for Unix manual pages (Groff/Troff) and the historical RUNOFF language.",
5 | "keywords": ["Roff", "Groff", "Troff", "Nroff", "Typesetting", "RUNOFF", "DSR", "BSD", "Unix Manual Page", "Manpage", "man",
6 | "mandoc", "mdoc", "Ronn", "Ditroff", "Eqn", "Pic", "Chem", "DFORMAT", "Grap", "Refer", "Tbl", "IDEAL", "grn", "Plan9"],
7 | "repository": "https://github.com/Alhadis/language-roff",
8 | "author": "John Gardner ",
9 | "license": "ISC",
10 | "engines": {"atom": ">=1.35.0"},
11 | "atomTestRunner": "atom-mocha",
12 | "scripts": {
13 | "lint": "npx eslint .",
14 | "test": "atom -t spec",
15 | "update-fixtures": "cd spec/fixtures && node update input/* && mv input/*.json output && node prune"
16 | },
17 | "dependencies": {
18 | "prompt-view": "^1.0.0",
19 | "roff": "^0.2.0"
20 | },
21 | "devDependencies": {
22 | "@alhadis/eslint-config": "^1.2.0",
23 | "atom-mocha": "^2.2.1",
24 | "eslint": "^6.4.0",
25 | "first-mate": "^7.4.3",
26 | "language-hyperlink": "atom/language-hyperlink"
27 | },
28 | "deserializers": {
29 | "TroffView": "createTroffView"
30 | },
31 | "configSchema": {
32 | "blankLineReplacement": {
33 | "type": "string",
34 | "title": "Blank-line replacement",
35 | "description": "Specify the string which replaces empty lines when running the `fix-blank-lines` command.",
36 | "default": ".",
37 | "order": 1
38 | },
39 | "linkManPageReferences": {
40 | "type": "boolean",
41 | "title": "Link man-page references",
42 | "description": "Make references to `man(1)` pages clickable inside comments and markup.",
43 | "default": true,
44 | "order": 2
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/settings/editor.cson:
--------------------------------------------------------------------------------
1 | ".text.roff":
2 | editor:
3 | commentStart: ".\\\" "
4 | softWrap: true
5 |
6 | ".text.roff .refer":
7 | editor:
8 | commentStart: "# "
9 |
10 | ".source.pic":
11 | editor:
12 | commentStart: "# "
13 | increaseIndentPattern: "[\\[({:]\\s*$|\\.cstart\\b"
14 | decreaseIndentPattern: "^\\s*[\\])}]|\\.cend\\b"
15 |
16 | ".source.ideal":
17 | editor:
18 | commentStart: "/* "
19 | commentEnd: " */"
20 |
21 | ".source.ditroff":
22 | editor:
23 | commentStart: "# "
24 | tabLength: 8
25 |
26 | ".source.context":
27 | editor:
28 | commentStart: "% "
29 |
30 | ".source.gremlin":
31 | editor:
32 | commentStart: ""
33 | preferredLineLength: 127
34 |
35 | ".text.runoff":
36 | editor:
37 | commentStart: ".! "
38 | softWrap: true
39 |
--------------------------------------------------------------------------------
/snippets/snippets.cson:
--------------------------------------------------------------------------------
1 | # NOTE: Each snippet has been duplicated to be used with or without a leading
2 | # period. If changes are made to one, ensure they're reflected in the other.
3 | # Atom snippets don't yet support multiple prefixes. See atom/snippets#107.
4 |
5 | ".text.roff":
6 |
7 | # Macro definition
8 | ".de":
9 | prefix: ".de"
10 | body: ".de ${1:NAME}\n.\t$3\n.."
11 | description: "Define a new macro"
12 | leftLabel: ".de"
13 |
14 | de:
15 | prefix: "de"
16 | body: ".de ${1:NAME}\n.\t$3\n.."
17 | description: "Define a new macro"
18 | leftLabel: ".de"
19 |
20 |
21 | # "If" block
22 | ".if":
23 | prefix: ".if"
24 | body: ".if ${1:COND} \\\\{\\\\\n.\t$2\n.\\\\}"
25 | description: "Multiline `if` statement"
26 | leftLabel: ".if"
27 |
28 | if:
29 | prefix: "if"
30 | body: ".if ${1:COND} \\\\{\\\\\n.\t$2\n.\\\\}"
31 | description: "Multiline `if` statement"
32 | leftLabel: ".if"
33 |
34 |
35 | # "If/else" block
36 | ".ie":
37 | prefix: ".ie"
38 | body: ".ie ${1:COND} \\\\{\\\\\n.\t$2\n.\\\\}\n.el \\\\{\\\\\n.\t$3\n.\\\\}"
39 | description: "Multiline `if/else` block"
40 | leftLabel: ".ie"
41 |
42 | ie:
43 | prefix: "ie"
44 | body: ".ie ${1:COND} \\\\{\\\\\n.\t$2\n.\\\\}\n.el \\\\{\\\\\n.\t$3\n.\\\\}"
45 | description: "Multiline `if/else` block"
46 | leftLabel: ".ie"
47 |
48 |
49 | # Manual pages
50 | manpage:
51 | prefix: "manpage"
52 | body: """
53 | .TH ${1:PROGNAME} 1
54 | .SH NAME
55 | ${1:progname} \\- ${2:short description}
56 | .SH SYNOPSIS
57 | .B ${1:progname}
58 | ${3:[command-line options]
59 | .\\\\" TIP: Use the `opt` snippet to insert options here}
60 | .SH DESCRIPTION
61 | ${4:-- Extended description --}
62 | .SH OPTIONS
63 | ${5:-- Options --}
64 | .SH EXAMPLES
65 | ${6:-- Examples --}
66 | $7
67 | """
68 | leftLabel: "man"
69 | description: "Template for a basic manual-page"
70 | descriptionMoreURL: "http://liw.fi/manpages/"
71 |
72 | opt:
73 | prefix: "opt"
74 | body: "[\\\\fB\\\\-${1:o}\\\\fR \\\\fI${2:value}\\\\fR]\n$3"
75 | leftLabel: "man"
76 | description: "Short option: [-o value]"
77 |
78 | optl:
79 | prefix: "optl"
80 | body: "[\\\\fB\\\\-\\\\-${1:option}\\\\fR \\\\fI${2:value}\\\\fR]\n$3"
81 | leftLabel: "man"
82 | description: "Long option: [--option value]"
83 |
84 | optinfo:
85 | prefix: "optinfo"
86 | body: '.TP\n.BI \\\\-${1:o} " ${2:value}" \\\\fR, "\\\\fB \\\\-\\\\-${3:option}" \\\\fR=\\\\fI${2:value}\\\\fR\n${4:Description}\n$5'
87 | leftLabel: "man"
88 | description: "Extended information about an option"
89 | descriptionMoreURL: "http://liw.fi/manpages/"
90 |
91 | sh:
92 | prefix: "sh"
93 | body: """.
94 | .\\\\" ${2:--------------------------------------------------------------------}
95 | .SH "${1:SECTION HEADING}"
96 | .\\\\" ${2:--------------------------------------------------------------------}
97 | .$3"""
98 | leftLabel: "man"
99 | description: "Bordered section heading"
100 |
101 |
102 | # Code snippets for manpages
103 | ".code":
104 | prefix: ".code"
105 | body: ".PP\n.nf\n.RS\n.ft C\n${1:Enter code block here}\n.ft\n.RE\n.fi\n.PP"
106 | leftLabel: "man"
107 | description: "Block of example code"
108 | descriptionMoreURL: "http://liw.fi/manpages/"
109 |
110 | code:
111 | prefix: "code"
112 | body: ".PP\n.nf\n.RS\n.ft C\n${1:Enter code block here}\n.ft\n.RE\n.fi\n.PP"
113 | leftLabel: "man"
114 | description: "Block of example code"
115 | descriptionMoreURL: "http://liw.fi/manpages/"
116 |
117 |
118 |
119 | # Mandoc template
120 | mandoc:
121 | prefix: "mandoc"
122 | body: """
123 | .Dd $Mdocdate$
124 | .Dt ${1:PROGNAME} 1
125 | .Os
126 | .Sh NAME
127 | .Nm ${1:progname}
128 | .Nd ${2:Short description}
129 | .Sh SYNOPSIS
130 | .Nm ${1:progname}
131 | .Op Fl ${3:options}
132 | .Ar ${4:arguments}
133 | .Sh DESCRIPTION
134 | ${5:-- Text --}
135 | .Sh ENVIRONMENT
136 | ${6:-- Text --}
137 | .Sh FILES
138 | ${7:-- Text --}
139 | .Sh EXIT STATUS
140 | ${8:-- Text --}
141 | .Sh EXAMPLES
142 | ${9:-- Text --}
143 | .Sh DIAGNOSTICS
144 | ${10:-- Text --}
145 | .Sh ERRORS
146 | ${11:-- Text --}
147 | .Sh SEE ALSO
148 | .Xr ${12:manpage} 1
149 | $13
150 | """
151 | leftLabel: "mdoc"
152 | description: "Skeleton for a mandoc manual page"
153 | descriptionMoreURL: "http://mdocml.bsd.lv/man/mdoc.7.html"
154 |
155 |
156 | # Table template
157 | tbl:
158 | prefix: "tbl"
159 | body: ".TS\nallbox;\nl c c .\n${1:Header1}\t${2:Header2}\t${3:Header3}\n${4:Body1\tBody2\tBody3}\n.TE\n"
160 | leftLabel: "tbl"
161 | description: "Template for a 3-column bordered table"
162 | descriptionMoreURL: "http://linux.die.net/man/1/tbl"
163 |
164 |
165 | # Debugging message
166 | log:
167 | prefix: "log"
168 | body: ".tm ${1:Text}"
169 | leftLabel: ".tm"
170 | description: "Trace message to STDERR"
171 |
172 |
173 | ".source.pic":
174 |
175 | # DFORMAT block
176 | ".dformat":
177 | prefix: ".df"
178 | body: ".begin dformat\nstyle $1\n.end"
179 |
180 | dformat:
181 | prefix: "df"
182 | body: ".begin dformat\nstyle $1\n.end"
183 |
184 |
185 | # CHEM: Dextroamphetamine molecule
186 | dextroamphetamine:
187 | prefix: "dex"
188 | body: """
189 | .cstart
190 | .ps 26
191 | size 28
192 | R1:
193 | ring double 1,2 3,4 5,6
194 | bond 60 from R1.V2
195 | bond 120
196 | A1:
197 | front bond down ; CH3
198 | bond 60 from A1 ; NH2
199 | .ps
200 | .cend
201 | """
202 |
--------------------------------------------------------------------------------
/spec/.mocharc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | ui: "bdd",
5 | slow: 3500,
6 | require: [
7 | "chai/register-expect",
8 | "chai/register-should",
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/spec/fixtures/input/art-1.pic:
--------------------------------------------------------------------------------
1 | .PS
2 | A: box "yin"; move;
3 | B: box "yang";
4 | arrow right at 1/4 ;
5 | arrow left at 1/4 ;
6 |
7 | box wid 0.5 ht 0.5;
8 | box wid 0.75 ht 0.75 with .sw at last box .se;
9 | .PE
10 |
11 | .PS
12 | circle "x"
13 | circle "y" at 1st circle - (0.4, 0.6)
14 | circle "z" at 1st circle + (0.4, -0.6)
15 | arrow from 1st circle to 2nd circle chop
16 | arrow from 2nd circle to 3rd circle chop
17 | arrow from 3rd circle to 1st circle chop
18 | A: [
19 | circle;
20 | line up 1 at last circle .n;
21 | line down 1 at last circle .s;
22 | line right 1 at last circle .e;
23 | line left 1 at last circle .w;
24 | box dashed with .nw at last circle .se + (0.2, -0.2);
25 | Caption: center of last box;
26 | ]
27 | .PE
28 |
29 | .PS
30 | # Plot a single jumper in a box, $1 is the on-off state.
31 | define jumper { [
32 | shrinkfactor = 0.8;
33 | Outer: box invis wid 0.45 ht 1;
34 | # Count on end ] to reset these
35 | boxwid = Outer.wid * shrinkfactor / 2;
36 | boxht = Outer.ht * shrinkfactor / 2;
37 | box fill (!$1) with .s at center of Outer;
38 | box fill ($1) with .n at center of Outer;
39 | ] }
40 |
41 | undef jumper
42 | undef jumperblock
43 | .PE
44 |
45 | .PS
46 | copy "filename.pic" thru XY
47 | .PE
48 |
49 | .PS
50 | copy thru % circle at ($1,$2) % until "END"
51 | 1 2
52 | 3 4
53 | 5 6
54 | END
55 | box
56 | .PE
57 |
58 | .PS
59 | x = 14
60 | command ".ds string x is " x "."
61 | .PE
62 | \*[string]
63 |
64 | .PS
65 | sh { printf anything }
66 | .PE
67 |
68 | .PS
69 | pi = atan2(0,-1);
70 | for i = 0 to 2 * pi by 0.1 do {
71 | "-" at (i/2, 0);
72 | "." at (i/2, sin(i)/2);
73 | ":" at (i/2, cos(i)/2);
74 | }
75 | .PE
76 |
77 | .PS
78 | copy thru { … } until "__END__"
79 | .PE
80 |
--------------------------------------------------------------------------------
/spec/fixtures/input/art-2.pic:
--------------------------------------------------------------------------------
1 | .PS
2 | # Draw a demonstration up left arrow with grid box overlay
3 | define gridarrow
4 | {
5 | move right 0.1
6 | [
7 | {arrow up left $1;}
8 | box wid 0.5 ht 0.5 dotted with .nw at last arrow .end;
9 | for i = 2 to ($1 / 0.5) do
10 | {
11 | box wid 0.5 ht 0.5 dotted with .sw at last box .se;
12 | }
13 | move down from last arrow .center;
14 | [
15 | sprintf("\fBarrow up left %g\fP", $1)
16 | ]
17 | ]
18 | move right 0.1 from last [] .e;
19 | }
20 | gridarrow(0.5);
21 | gridarrow(1);
22 | gridarrow(1.5);
23 | gridarrow(2);
24 | undef gridarrow
25 | .PE
26 |
--------------------------------------------------------------------------------
/spec/fixtures/input/art-3.pic:
--------------------------------------------------------------------------------
1 | .PS
2 | print 1
3 | print -1
4 | print 1.5
5 | print -1.5
6 | print 1e0
7 | print 1E0
8 | print 1e+0
9 | print 1E+0
10 | print 1e-0
11 | print 1E-0
12 | print -1e0
13 | print -1E0
14 | print -1e+0
15 | print -1E+0
16 | print -1e-0
17 | print -1E-0
18 | print 1.5e0
19 | print 1.5E0
20 | print 1.5e+0
21 | print 1.5E+0
22 | print 1.5e-0
23 | print 1.5E-0
24 | print -1.5e0
25 | print -1.5E0
26 | print -1.5e+0
27 | print -1.5E+0
28 | print -1.5e-0
29 | print -1.5E-0
30 | print 1.e0
31 | print 1.E0
32 | print 1.e+0
33 | print 1.E+0
34 | print 1.e-0
35 | print 1.E-0
36 | print -1.e0
37 | print -1.E0
38 | print -1.e+0
39 | print -1.E+0
40 | print -1.e-0
41 | print -1.E-0
42 | .PE
43 |
--------------------------------------------------------------------------------
/spec/fixtures/input/art.pikchr:
--------------------------------------------------------------------------------
1 | // Pikchr extensions
2 | /* https://pikchr.org/home/doc/trunk/doc/differences.md */
3 |
4 | # 1. Unicode arrows
5 | <- ← ← ←
6 | -> → → →
7 | <-> ↔ ↔
8 |
9 | # 2. New object types
10 | oval
11 | cylinder
12 | file
13 | dot
14 |
15 | # 3. Units other than inches
16 | 1.5in; 1.5
17 | 2.3cm
18 | 230mm
19 | 13pc
20 | 24pt
21 | 48px
22 |
23 | # 4. Chamfered corners
24 | box radius 15px
25 | arrow radius 10px
26 |
27 | # 5. Colours
28 | box color blue
29 | box fill lightgray
30 | box color white fill blue
31 |
32 | # 6. Border thickness
33 | box thickness 1.5
34 | box thin
35 | box thick
36 | box thick thick
37 |
38 | # 7. Text modifiers
39 | box "Emboldened" bold
40 | box "Italicised" italic
41 | box "Bigger" big
42 | box "Smaller" small
43 | line above aligned
44 |
45 | # 8. Auto-fitting
46 | box "Text" "labels" fit
47 |
48 | # 9. Percentile property values
49 | box "default" italic "box" italic
50 | move; box "width 150%" width 150%
51 | move; box "wid 75%" wid 75%
52 |
53 | # 10. Chop semantics
54 | arrow <-> from A to B chop "from A to B chop" aligned above
55 |
56 | # 11. `same as …` construct
57 | file same as last box
58 |
59 | # 12. Line paths
60 | go 1cm heading 45.0
61 | go 1cm 90.0
62 | go right until even with 100%
63 | close
64 |
65 | # 13. Positions
66 | 1cm above previous.n
67 | 1cm below previous.s
68 | 1cm left of previous.nw
69 | 1cm right of previous.se
70 | 1cm heading 45.0 from box.s
71 | 2nd vertex of first line
72 | cylinder "B" at 5cm heading 125 from A
73 |
74 | # 14. Prior objects
75 | last; previous
76 |
77 | # 15. C/C++ comments
78 | # Traditional comment
79 | // C++-style comment
80 | /* C comment */
81 |
82 | /* <<<<<<<<<<<<<<
83 | <<<<*/ box /*>>>>
84 | >>>>>>>>>>>>>>>>> */
85 |
86 | # 16. Variables beginning with `$` or `@`
87 | $argc
88 | @argv
89 |
90 | # 17. Assignment operators
91 | x = 1.5
92 | x += 1.5
93 | x -= 1.5
94 | x *= 1.5
95 | x /= 1.5
96 | x := 1.5
97 | 1/3
98 | circlerad *= 0.75
99 |
100 | !not && || == != >= <= < >
101 |
102 |
103 |
104 | arrow right 200% "Markdown" "Source"
105 | box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
106 | arrow right 200% "HTML+SVG" "Output"
107 | arrow <-> down 70% from last box.s
108 | arrow ↔ down 70% from last box.s
109 | box same "Pikchr" "Formatter" "(pikchr.c)" fit
110 |
111 | line from 1cm right of previous.se to 3cm right of previous.ne \
112 | "aligned" above aligned
113 |
114 |
115 |
116 |
117 |
118 | text "Foo"
119 |
--------------------------------------------------------------------------------
/spec/fixtures/input/colours.pikchr:
--------------------------------------------------------------------------------
1 | circle "0xF0F8FF" fill AliceBlue
2 | circle "0xFAEBD7" fill AntiqueWhite
3 | circle "0x00FFFF" fill Aqua
4 | circle "0x7FFFD4" fill Aquamarine
5 | circle "0xF0FFFF" fill Azure
6 | circle "0xF5F5DC" fill Beige
7 | circle "0xFFE4C4" fill Bisque
8 | circle "0x000000" fill Black
9 | circle "0xFFEBCD" fill BlanchedAlmond
10 | circle "0x0000FF" fill Blue
11 | circle "0x8A2BE2" fill BlueViolet
12 | circle "0xA52A2A" fill Brown
13 | circle "0xDEB887" fill BurlyWood
14 | circle "0x5F9EA0" fill CadetBlue
15 | circle "0x7FFF00" fill Chartreuse
16 | circle "0xD2691E" fill Chocolate
17 | circle "0xFF7F50" fill Coral
18 | circle "0x6495ED" fill CornflowerBlue
19 | circle "0xFFF8DC" fill Cornsilk
20 | circle "0xDC143C" fill Crimson
21 | circle "0x00FFFF" fill Cyan
22 | circle "0x00008B" fill DarkBlue
23 | circle "0x008B8B" fill DarkCyan
24 | circle "0xB8860B" fill DarkGoldenrod
25 | circle "0xA9A9A9" fill DarkGray
26 | circle "0x006400" fill DarkGreen
27 | circle "0xA9A9A9" fill DarkGrey
28 | circle "0xBDB76B" fill DarkKhaki
29 | circle "0x8B008B" fill DarkMagenta
30 | circle "0x556B2F" fill DarkOliveGreen
31 | circle "0xFF8C00" fill DarkOrange
32 | circle "0x9932CC" fill DarkOrchid
33 | circle "0x8B0000" fill DarkRed
34 | circle "0xE9967A" fill DarkSalmon
35 | circle "0x8FBC8F" fill DarkSeaGreen
36 | circle "0x483D8B" fill DarkSlateBlue
37 | circle "0x2F4F4F" fill DarkSlateGray
38 | circle "0x2F4F4F" fill DarkSlateGrey
39 | circle "0x00CED1" fill DarkTurquoise
40 | circle "0x9400D3" fill DarkViolet
41 | circle "0xFF1493" fill DeepPink
42 | circle "0x00BFFF" fill DeepSkyBlue
43 | circle "0x696969" fill DimGray
44 | circle "0x696969" fill DimGrey
45 | circle "0x1E90FF" fill DodgerBlue
46 | circle "0xB22222" fill Firebrick
47 | circle "0xFFFAF0" fill FloralWhite
48 | circle "0x228B22" fill ForestGreen
49 | circle "0xFF00FF" fill Fuchsia
50 | circle "0xDCDCDC" fill Gainsboro
51 | circle "0xF8F8FF" fill GhostWhite
52 | circle "0xFFD700" fill Gold
53 | circle "0xDAA520" fill Goldenrod
54 | circle "0x808080" fill Gray
55 | circle "0x008000" fill Green
56 | circle "0xADFF2F" fill GreenYellow
57 | circle "0x808080" fill Grey
58 | circle "0xF0FFF0" fill Honeydew
59 | circle "0xFF69B4" fill HotPink
60 | circle "0xCD5C5C" fill IndianRed
61 | circle "0x4B0082" fill Indigo
62 | circle "0xFFFFF0" fill Ivory
63 | circle "0xF0E68C" fill Khaki
64 | circle "0xE6E6FA" fill Lavender
65 | circle "0xFFF0F5" fill LavenderBlush
66 | circle "0x7CFC00" fill LawnGreen
67 | circle "0xFFFACD" fill LemonChiffon
68 | circle "0xADD8E6" fill LightBlue
69 | circle "0xF08080" fill LightCoral
70 | circle "0xE0FFFF" fill LightCyan
71 | circle "0xFAFAD2" fill LightGoldenrodYellow
72 | circle "0xD3D3D3" fill LightGray
73 | circle "0x90EE90" fill LightGreen
74 | circle "0xD3D3D3" fill LightGrey
75 | circle "0xFFB6C1" fill LightPink
76 | circle "0xFFA07A" fill LightSalmon
77 | circle "0x20B2AA" fill LightSeaGreen
78 | circle "0x87CEFA" fill LightSkyBlue
79 | circle "0x778899" fill LightSlateGray
80 | circle "0x778899" fill LightSlateGrey
81 | circle "0xB0C4DE" fill LightSteelBlue
82 | circle "0xFFFFE0" fill LightYellow
83 | circle "0x00FF00" fill Lime
84 | circle "0x32CD32" fill LimeGreen
85 | circle "0xFAF0E6" fill Linen
86 | circle "0xFF00FF" fill Magenta
87 | circle "0x800000" fill Maroon
88 | circle "0x66CDAA" fill MediumAquamarine
89 | circle "0x0000CD" fill MediumBlue
90 | circle "0xBA55D3" fill MediumOrchid
91 | circle "0x9370DB" fill MediumPurple
92 | circle "0x3CB371" fill MediumSeaGreen
93 | circle "0x7B68EE" fill MediumSlateBlue
94 | circle "0x00FA9A" fill MediumSpringGreen
95 | circle "0x48D1CC" fill MediumTurquoise
96 | circle "0xC71585" fill MediumVioletRed
97 | circle "0x191970" fill MidnightBlue
98 | circle "0xF5FFFA" fill MintCream
99 | circle "0xFFE4E1" fill MistyRose
100 | circle "0xFFE4B5" fill Moccasin
101 | circle "0xFFDEAD" fill NavajoWhite
102 | circle "0x000080" fill Navy
103 | circle "-1" fill None
104 | circle "-1" fill Off
105 | circle "0xFDF5E6" fill OldLace
106 | circle "0x808000" fill Olive
107 | circle "0x6B8E23" fill OliveDrab
108 | circle "0xFFA500" fill Orange
109 | circle "0xFF4500" fill OrangeRed
110 | circle "0xDA70D6" fill Orchid
111 | circle "0xEEE8AA" fill PaleGoldenrod
112 | circle "0x98FB98" fill PaleGreen
113 | circle "0xAFEEEE" fill PaleTurquoise
114 | circle "0xDB7093" fill PaleVioletRed
115 | circle "0xFFEFD5" fill PapayaWhip
116 | circle "0xFFDAB9" fill PeachPuff
117 | circle "0xCD853F" fill Peru
118 | circle "0xFFC0CB" fill Pink
119 | circle "0xDDA0DD" fill Plum
120 | circle "0xB0E0E6" fill PowderBlue
121 | circle "0x800080" fill Purple
122 | circle "0x663399" fill RebeccaPurple
123 | circle "0xFF0000" fill Red
124 | circle "0xBC8F8F" fill RosyBrown
125 | circle "0x4169E1" fill RoyalBlue
126 | circle "0x8B4513" fill SaddleBrown
127 | circle "0xFA8072" fill Salmon
128 | circle "0xF4A460" fill SandyBrown
129 | circle "0x2E8B57" fill SeaGreen
130 | circle "0xFFF5EE" fill Seashell
131 | circle "0xA0522D" fill Sienna
132 | circle "0xC0C0C0" fill Silver
133 | circle "0x87CEEB" fill SkyBlue
134 | circle "0x6A5ACD" fill SlateBlue
135 | circle "0x708090" fill SlateGray
136 | circle "0x708090" fill SlateGrey
137 | circle "0xFFFAFA" fill Snow
138 | circle "0x00FF7F" fill SpringGreen
139 | circle "0x4682B4" fill SteelBlue
140 | circle "0xD2B48C" fill Tan
141 | circle "0x008080" fill Teal
142 | circle "0xD8BFD8" fill Thistle
143 | circle "0xFF6347" fill Tomato
144 | circle "0x40E0D0" fill Turquoise
145 | circle "0xEE82EE" fill Violet
146 | circle "0xF5DEB3" fill Wheat
147 | circle "0xFFFFFF" fill White
148 | circle "0xF5F5F5" fill WhiteSmoke
149 | circle "0xFFFF00" fill Yellow
150 | circle "0x9ACD32" fill YellowGreen
151 |
--------------------------------------------------------------------------------
/spec/fixtures/input/context.sqtroff:
--------------------------------------------------------------------------------
1 | X ps 300 1 1
2 | Y P default letter 2550 3300 0 0 0 0 2550 3300
3 | x a 20
4 | x b 10
5 | x bd 2 3
6 | x cs 10 20
7 | x Slant 45
8 | x Height 20
9 |
10 | N n l 3 1234
11 | = (This is a test.)
12 | n
13 |
14 | N n l 3 1134
15 | = (This )
16 | h 50
17 | f 10 TI
18 | = (is)
19 | h 50
20 | f 10 TR
21 | = ( a test.)
22 | n
23 |
24 | X impr 300 1 1
25 | Y P default letter 2550 3300 21 17 56 25 2400 3210
26 | P 1
27 | V 50
28 | O 289
29 | w 12
30 | L 1950
31 | I 0
32 | N b 1 6 1432
33 | f 10 TR
34 | k 0 1
35 | D w 0
36 | = (This is a sample line of text.)
37 | n
38 | N b 1 6 1308
39 | = (This is another sample line of text.)
40 | n
41 | N b 1 7 1230
42 | = (This is still another sample line of text.)
43 | n
44 | p 3300
45 |
46 | % Ordinary comment
47 | % D message
48 | % E Error message
49 | % I fp 2 HelveticaBold
50 | % T Trace line
51 | % W Warning message
52 |
53 | % Line drawing
54 | D a 2 1 2 0
55 | D b 100 100
56 | D c 500
57 | D e 500 700
58 | D f 99
59 | D l 100 500
60 | D t 7
61 | D w 128
62 | D ~ 0 0 100 100
63 |
64 | E cmd tr a-z A-Z
65 | E nl on
66 | E oc *
67 | E < /dev/null
68 | e Text passed directly to device
69 |
70 | f 12 Times New Roman
71 | h 140
72 | v -20
73 | I 4
74 | k 120 1
75 | L 77
76 | O 24
77 |
78 | N f j 8 480
79 | R h 10 ru
80 | R v 10 rn
81 | R t h 20
82 | R t v 10
83 | s -2
84 | V 15
85 | w 240
86 | = (O)
87 | z
88 | = (/)
89 | n
90 |
91 |
92 | z Invalid, unused arguments
93 |
94 | % A sentence logically can't be contained inside a bigger
95 | % sentence, so a nested line command doesn't make sense.
96 | N n l 2 840
97 | N n l 2 840
98 | n
99 |
100 | % An “n” without a corresponding “N” isn't valid either
101 | n
102 |
103 | % The following line isn't a valid Context command
104 | yeah nah garn m8
105 |
--------------------------------------------------------------------------------
/spec/fixtures/input/ditroff.1:
--------------------------------------------------------------------------------
1 | \X'ps: exec \n(XY showpage'
2 | \X'html: \*(XY'
3 | \X'\*(XY'
4 |
5 | .output tFoo tBar
6 | .output x X tty: sgr 1
7 | .output # Device name is \*(.T
8 | .output D F g
9 | .output w0 0
10 | .output t\
11 | Foo\
12 | Bar 0
13 |
14 | .output # Comment. \
15 | Also commented.
16 | Normal text.
17 |
18 | .ds TM \s'-0.5m'\uTM\d\s'+0.5m'
19 | .output #
20 | .output # COPY MODE:
21 | .output # \\*(TM gets interpreted: \*(TM
22 | .output # However, \\(tm does not: \(tm
23 | .output # \
24 | Normal text; previous line ended with `\e `.
25 |
--------------------------------------------------------------------------------
/spec/fixtures/input/ditroff.2:
--------------------------------------------------------------------------------
1 | x T s
2 | x res 1 2 3
3 | x init
4 | x font 1 s
5 | x font 2 s path/to/filename 1
6 | x pause
7 | x stop
8 | x trailer
9 | x Height 1
10 | x Height -23 3.1
11 | x Slant 1
12 | x X Anchor 1,2 foo
13 | x X Anchor foo
14 | x X BleedAt 1 2 3 4
15 | x X CropAt 1 2 3 4
16 | x X HorScale 23
17 | x X LC_CTYPE en_US.UTF-8
18 | x X Link 1,2,3,4 id
19 | x X Link id
20 | x X Link
21 | x X PaperSize 595000 842000 1
22 | x X PS command % It's real, baby
23 | x X PSSetup command
24 | x X SetColor 12.0 3 rgb
25 | x X SupplyFont font file
26 | x X Sync
27 | x X Track 1
28 | x X TrimAt 1 2 3 4
29 | x X ULink 1,2,3,4 http://URL
30 | x X ULink https://URL
31 | x X ULink
32 | x X any
33 | x any
34 |
35 | x X PDFMark: Bookmark 0 Text
36 | x X PDFMark: Author Gunnar Ritter
37 | x X PDFMark: Keywords Heirloom Documentation Tools, troff
38 | x X PDFMark: Title Heirloom Documentation Tools: Quickstart Guide
39 | x X PS: [ {Catalog} << /ViewerPreferences << /DisplayDocTitle true >> >> /PUT pdfmark
40 |
41 | x X ps: file /path/to/file.ps
42 | x X ps: mdef 1 = (Insert code here.)
43 | x X ps: def = (Insert code here.)
44 | x X ps: import file 1 2 3 4 5 6
45 |
46 | x X papersize=A1
47 | x X pdf: pdfpic /path/to/picture.pdf -L 3 4 5
48 | x X pdf: xrev
49 | x X pdf: markstart /ANN definition
50 | x X pdf: markend
51 | x X pdf: marksuspend
52 | x X pdf: markrestart
53 | x X pdf: transition SLIDE Blinds 1.645 H O 50 42.1 true
54 | x X pdf: background pagefillbox 0 0 100 100 1.5
55 | x X pdf: background off
56 | x X pdf: background footnote bottom
57 | x X tty: sgr 1
58 | x X pdf: pagename foo
59 | x X pdf: switchtopage after foo
60 |
61 | x X html:Foo
62 | x X html: …could
63 | x X html: