├── .appveyor.yml
├── .gitattributes
├── .github
└── stale.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── keymaps
└── latex.json
├── lib
├── build-state.js
├── builder-registry.js
├── builder.js
├── builders
│ ├── knitr.js
│ └── latexmk.js
├── composer.js
├── config-migrator.js
├── latex.js
├── logger.js
├── main.js
├── marker-manager.js
├── opener-registry.js
├── opener.js
├── openers
│ ├── atril-opener.js
│ ├── custom-opener.js
│ ├── evince-opener.js
│ ├── okular-opener.js
│ ├── pdf-view-opener.js
│ ├── preview-opener.js
│ ├── qpdfview-opener.js
│ ├── shell-open-opener.js
│ ├── skim-opener.js
│ ├── sumatra-opener.js
│ ├── x-reader-opener.js
│ ├── xdg-open-opener.js
│ └── zathura-opener.js
├── parser.js
├── parsers
│ ├── fdb-parser.js
│ ├── log-parser.js
│ └── magic-parser.js
├── process-manager.js
├── status-indicator.js
├── views
│ ├── file-reference.js
│ ├── log-dock.js
│ ├── log-message.js
│ ├── message-count.js
│ ├── message-icon.js
│ └── status-label.js
└── werkzeug.js
├── menus
└── latex.json
├── package.json
├── resources
└── latexmkrc
├── script
├── cibuild
├── create-fixtures
├── install-ghostscript
├── install-knitr
├── install-miktex
├── install-texlive
└── lint
├── spec
├── async-spec-helpers.js
├── build-registry-spec.js
├── builder-spec.js
├── builders
│ ├── knitr-spec.js
│ └── latexmk-spec.js
├── composer-spec.js
├── fixtures
│ ├── _minted-wibble
│ │ └── default.pygstyle
│ ├── error-warning.tex
│ ├── errors.log
│ ├── file.fdb_latexmk
│ ├── file.log
│ ├── file.tex
│ ├── filename with spaces.log
│ ├── filename with spaces.tex
│ ├── knitr
│ │ └── file.Rnw
│ ├── latexmk
│ │ ├── asymptote-test.tex
│ │ ├── glossaries-test.tex
│ │ ├── index-test.tex
│ │ ├── mpost-test.tex
│ │ ├── nomencl-test.tex
│ │ └── sagetex-test.tex
│ ├── log-parse
│ │ ├── file-dvi.fdb_latexmk
│ │ ├── file-dvi.log
│ │ ├── file-pdf.fdb_latexmk
│ │ ├── file-pdf.log
│ │ ├── file-pdfdvi.fdb_latexmk
│ │ ├── file-pdfdvi.log
│ │ ├── file-pdfps.fdb_latexmk
│ │ ├── file-pdfps.log
│ │ ├── file-ps.fdb_latexmk
│ │ └── file-ps.log
│ ├── magic-comments
│ │ ├── multiple-magic-comments.tex
│ │ ├── no-whitespace.tex
│ │ ├── not-first-line.tex
│ │ ├── override-settings.tex
│ │ ├── override-settings.yaml
│ │ └── root-comment.tex
│ ├── master-tex-finder
│ │ ├── multiple-masters
│ │ │ ├── inc1.tex
│ │ │ ├── inc2.tex
│ │ │ ├── master1.tex
│ │ │ └── master2.tex
│ │ └── single-master
│ │ │ ├── inc1.tex
│ │ │ ├── inc2.tex
│ │ │ ├── inc3.tex
│ │ │ ├── master.log
│ │ │ └── master.tex
│ └── sub
│ │ ├── foo bar.tex
│ │ └── wibble.tex
├── latex-spec.js
├── logger-spec.js
├── marker-manager-spec.js
├── opener-registry-spec.js
├── parsers
│ ├── fdb-parser-spec.js
│ ├── log-parser-spec.js
│ └── magic-parser-spec.js
├── process-manager-spec.js
├── spec-helpers.js
└── stubs.js
└── styles
├── latex.less
└── markers.atom-text-editor.less
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | only:
3 | - master
4 |
5 | skip_tags: true
6 | clone_depth: 1
7 |
8 | image: Visual Studio 2015
9 |
10 | install:
11 | - ps: Install-Product node 5
12 |
13 | environment:
14 | global:
15 | MIKTEX_HOME: '%PROGRAMFILES%\MiKTeX 2.9'
16 | R_HOME: '%PROGRAMFILES%\R'
17 | R_LIBS_USER: '%USERPROFILE%\R\library'
18 | APM_TEST_PACKAGES: "language-latex pdf-view"
19 | matrix:
20 | - ATOM_CHANNEL: stable
21 | - ATOM_CHANNEL: beta
22 | - TEX_DIST: miktex
23 | ATOM_CHANNEL: stable
24 | - TEX_DIST: miktex
25 | ATOM_CHANNEL: beta
26 |
27 | cache:
28 | - '%R_LIBS_USER%'
29 |
30 | build_script:
31 | - set PATH=C:\Program Files\Git\mingw64\bin;%PATH%
32 | - sh ./script/cibuild
33 |
34 | test: off
35 | deploy: off
36 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | latexmkrc linguist-language=Perl
2 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | only: issues
2 | staleLabel: stale
3 | daysUntilStale: 90
4 | daysUntilClose: 14
5 |
6 | markComment: >
7 | This issue has been marked as stale because due to inactivity.
8 | It will be closed in 14 days if no further activity occurs.
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: generic
2 |
3 | branches:
4 | only:
5 | - master
6 |
7 | git:
8 | depth: 1
9 |
10 | os:
11 | - linux
12 | - osx
13 |
14 | sudo: false
15 | dist: trusty
16 | osx_image: xcode8
17 |
18 | env:
19 | global:
20 | - MAKE="make -j8"
21 | - R_LIBS_USER=${HOME}/R/library
22 | - APM_TEST_PACKAGES="language-latex pdf-view"
23 | matrix:
24 | - ATOM_CHANNEL=stable
25 | - ATOM_CHANNEL=beta
26 |
27 | addons:
28 | apt:
29 | sources:
30 | - ubuntu-toolchain-r-test
31 | - r-packages-trusty
32 | packages:
33 | - gcc-6
34 | - g++-6
35 | - r-recommended
36 | - freeglut3
37 | - ghostscript
38 |
39 | cache:
40 | directories:
41 | - ${R_LIBS_USER}
42 |
43 | before_script:
44 | - if [[ "${TRAVIS_OS_NAME:-}" == "linux" ]]; then export CC=gcc-6 CXX=g++-6; fi
45 |
46 | script: ./script/cibuild
47 |
48 | notifications:
49 | email:
50 | on_success: never
51 | on_failure: change
52 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | [INSERT CONTACT METHOD].
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series
87 | of actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or
94 | permanent ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within
114 | the community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.0, available at
120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121 |
122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
123 | enforcement ladder](https://github.com/mozilla/diversity).
124 |
125 | [homepage]: https://www.contributor-covenant.org
126 |
127 | For answers to common questions about this code of conduct, see the FAQ at
128 | https://www.contributor-covenant.org/faq. Translations are available at
129 | https://www.contributor-covenant.org/translations.
130 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 | First and foremost, thanks for contributing! I'm only one person and for this
3 | package to truly provide a first-class LaTeX experience from within Atom, your
4 | contributions are crucial.
5 |
6 | Before you set out on your adventure, please read this document carefully. It
7 | will save everyone involved both time and energy.
8 |
9 | ## Code of Conduct
10 | [](CODE_OF_CONDUCT.md)
11 |
12 | This project is governed by our [Code of Conduct](CODE_OF_CONDUCT.md).
13 | By participating, you are expected to uphold this code.
14 |
15 | ## Styleguides
16 | The short story is to use common sense and follow the existing styles and
17 | conventions, whether it's Git commit messages, JavaScript code, documentation,
18 | and so on.
19 |
20 | ### Git Commit Messages
21 | - The first line should be a short summary limited to 50 characters.
22 | - All other lines should be limited to 72 characters.
23 | - The first line and the (optional) detailed summary **must** always be
24 | separated by a single blank line since many tools rely on this convention.
25 | - Additional paragraphs should also be separated with blank lines,
26 | - Use the **present tense**; “Add feature” and not “Added feature”.
27 | - Use the **imperative mood**; “Fix bug …” and not “Fixes bug …”.
28 | - Reference issues and pull requests. If a commit resolves an issue, mention
29 | that in the summary, and prefix the reference with either “close”, “fix”,
30 | or “resolve” to automatically close the referenced issues when your pull
31 | requests are merged.
32 |
33 | See https://help.github.com/articles/closing-issues-via-commit-messages/.
34 |
35 | For additional details and inspiration, see [Tim Pope's excellent post][1] on
36 | commit message best practises.
37 |
38 | ### JavaScript Styleguide
39 | [][2]
40 |
41 | This project follows the [JavaScript Standard Style][2]. Compliance with the
42 | rules is automatically checked during CI builds. If you want to check if your
43 | changes are adhering to the rules, simply execute `script/lint`.
44 |
45 |
46 |
47 | [1]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
48 | [2]: http://standardjs.com/
49 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Thomas Johansen.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/keymaps/latex.json:
--------------------------------------------------------------------------------
1 | {
2 | "atom-text-editor[data-grammar~=\"latex\"]": {
3 | "ctrl-alt-b": "latex:build",
4 | "ctrl-alt-s": "latex:sync",
5 | "ctrl-alt-c": "latex:clean"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/build-state.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import path from 'path'
4 | import { isTexFile, isKnitrFile } from './werkzeug'
5 |
6 | function toArray (value) {
7 | return (typeof value === 'string') ? value.split(',').map(item => item.trim()) : Array.from(value)
8 | }
9 |
10 | function toBoolean (value) {
11 | return (typeof value === 'string') ? !!value.match(/^(true|yes)$/i) : !!value
12 | }
13 |
14 | class JobState {
15 | constructor (parent, jobName) {
16 | this.parent = parent
17 | this.jobName = jobName
18 | }
19 |
20 | getOutputFilePath () {
21 | return this.outputFilePath
22 | }
23 |
24 | setOutputFilePath (value) {
25 | this.outputFilePath = value
26 | }
27 |
28 | getFileDatabase () {
29 | return this.fileDatabase
30 | }
31 |
32 | setFileDatabase (value) {
33 | this.fileDatabase = value
34 | }
35 |
36 | getLogMessages () {
37 | return this.logMessages
38 | }
39 |
40 | setLogMessages (value) {
41 | this.logMessages = value
42 | }
43 |
44 | getJobName () {
45 | return this.jobName
46 | }
47 |
48 | getFilePath () {
49 | return this.parent.getFilePath()
50 | }
51 |
52 | getProjectPath () {
53 | return this.parent.getProjectPath()
54 | }
55 |
56 | getTexFilePath () {
57 | return this.parent.getTexFilePath()
58 | }
59 |
60 | setTexFilePath (value) {
61 | this.parent.setTexFilePath(value)
62 | }
63 |
64 | getKnitrFilePath () {
65 | return this.parent.getKnitrFilePath()
66 | }
67 |
68 | setKnitrFilePath (value) {
69 | this.parent.setKnitrFilePath(value)
70 | }
71 |
72 | getCleanPatterns () {
73 | return this.parent.getCleanPatterns()
74 | }
75 |
76 | getEnableSynctex () {
77 | return this.parent.getEnableSynctex()
78 | }
79 |
80 | getEnableShellEscape () {
81 | return this.parent.getEnableShellEscape()
82 | }
83 |
84 | getEnableExtendedBuildMode () {
85 | return this.parent.getEnableExtendedBuildMode()
86 | }
87 |
88 | getOpenResultAfterBuild () {
89 | return this.parent.getOpenResultAfterBuild()
90 | }
91 |
92 | getEngine () {
93 | return this.parent.getEngine()
94 | }
95 |
96 | getMoveResultToSourceDirectory () {
97 | return this.parent.getMoveResultToSourceDirectory()
98 | }
99 |
100 | getOutputDirectory () {
101 | return this.parent.getOutputDirectory()
102 | }
103 |
104 | getOutputFormat () {
105 | return this.parent.getOutputFormat()
106 | }
107 |
108 | getProducer () {
109 | return this.parent.getProducer()
110 | }
111 |
112 | getShouldRebuild () {
113 | return this.parent.getShouldRebuild()
114 | }
115 | }
116 |
117 | export default class BuildState {
118 | constructor (filePath, jobNames = [null], shouldRebuild = false) {
119 | this.setFilePath(filePath)
120 | this.setJobNames(jobNames)
121 | this.setShouldRebuild(shouldRebuild)
122 | this.setEnableSynctex(false)
123 | this.setEnableShellEscape(false)
124 | this.setEnableExtendedBuildMode(false)
125 | this.setOpenResultAfterBuild(false)
126 | this.subfiles = new Set()
127 | }
128 |
129 | getKnitrFilePath () {
130 | return this.knitrFilePath
131 | }
132 |
133 | setKnitrFilePath (value) {
134 | this.knitrFilePath = value
135 | }
136 |
137 | getTexFilePath () {
138 | return this.texFilePath
139 | }
140 |
141 | setTexFilePath (value) {
142 | this.texFilePath = value
143 | }
144 |
145 | getProjectPath () {
146 | return this.projectPath
147 | }
148 |
149 | setProjectPath (value) {
150 | this.projectPath = value
151 | }
152 |
153 | getCleanPatterns () {
154 | return this.cleanPatterns
155 | }
156 |
157 | setCleanPatterns (value) {
158 | this.cleanPatterns = toArray(value)
159 | }
160 |
161 | getEnableSynctex () {
162 | return this.enableSynctex
163 | }
164 |
165 | setEnableSynctex (value) {
166 | this.enableSynctex = toBoolean(value)
167 | }
168 |
169 | getEnableShellEscape () {
170 | return this.enableShellEscape
171 | }
172 |
173 | setEnableShellEscape (value) {
174 | this.enableShellEscape = toBoolean(value)
175 | }
176 |
177 | getEnableExtendedBuildMode () {
178 | return this.enableExtendedBuildMode
179 | }
180 |
181 | setEnableExtendedBuildMode (value) {
182 | this.enableExtendedBuildMode = toBoolean(value)
183 | }
184 |
185 | getOpenResultAfterBuild () {
186 | return this.openResultAfterBuild
187 | }
188 |
189 | setOpenResultAfterBuild (value) {
190 | this.openResultAfterBuild = toBoolean(value)
191 | }
192 |
193 | getEngine () {
194 | return this.engine
195 | }
196 |
197 | setEngine (value) {
198 | this.engine = value
199 | }
200 |
201 | getJobStates () {
202 | return this.jobStates
203 | }
204 |
205 | setJobStates (value) {
206 | this.jobStates = value
207 | }
208 |
209 | getMoveResultToSourceDirectory () {
210 | return this.moveResultToSourceDirectory
211 | }
212 |
213 | setMoveResultToSourceDirectory (value) {
214 | this.moveResultToSourceDirectory = toBoolean(value)
215 | }
216 |
217 | getOutputFormat () {
218 | return this.outputFormat
219 | }
220 |
221 | setOutputFormat (value) {
222 | this.outputFormat = value
223 | }
224 |
225 | getOutputDirectory () {
226 | return this.outputDirectory
227 | }
228 |
229 | setOutputDirectory (value) {
230 | this.outputDirectory = value
231 | }
232 |
233 | getProducer () {
234 | return this.producer
235 | }
236 |
237 | setProducer (value) {
238 | this.producer = value
239 | }
240 |
241 | getSubfiles () {
242 | return Array.from(this.subfiles.values())
243 | }
244 |
245 | addSubfile (value) {
246 | this.subfiles.add(value)
247 | }
248 |
249 | hasSubfile (value) {
250 | return this.subfiles.has(value)
251 | }
252 |
253 | getShouldRebuild () {
254 | return this.shouldRebuild
255 | }
256 |
257 | setShouldRebuild (value) {
258 | this.shouldRebuild = toBoolean(value)
259 | }
260 |
261 | getFilePath () {
262 | return this.filePath
263 | }
264 |
265 | setFilePath (value) {
266 | this.filePath = value
267 | this.texFilePath = isTexFile(value) ? value : undefined
268 | this.knitrFilePath = isKnitrFile(value) ? value : undefined
269 | this.projectPath = path.dirname(value)
270 | }
271 |
272 | getJobNames () {
273 | return this.jobStates.map(jobState => jobState.getJobName())
274 | }
275 |
276 | setJobNames (value) {
277 | this.jobStates = toArray(value).map(jobName => new JobState(this, jobName))
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/lib/builder-registry.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import path from 'path'
5 |
6 | export default class BuilderRegistry {
7 | getBuilderImplementation (state) {
8 | const builders = this.getAllBuilders()
9 | const candidates = builders.filter(builder => builder.canProcess(state))
10 | switch (candidates.length) {
11 | case 0: return null
12 | case 1: return candidates[0]
13 | }
14 |
15 | // This should never happen...
16 | throw new Error('Ambiguous builder registration.')
17 | }
18 |
19 | getBuilder (state) {
20 | const BuilderImpl = this.getBuilderImplementation(state)
21 | return (BuilderImpl != null) ? new BuilderImpl() : null
22 | }
23 |
24 | async checkRuntimeDependencies () {
25 | const builders = this.getAllBuilders()
26 | for (const BuilderImpl of builders) {
27 | const builder = new BuilderImpl()
28 | await builder.checkRuntimeDependencies()
29 | }
30 | }
31 |
32 | getAllBuilders () {
33 | const moduleDir = this.getModuleDirPath()
34 | const entries = fs.readdirSync(moduleDir)
35 | const builders = entries.map(entry => require(path.join(moduleDir, entry)))
36 |
37 | return builders
38 | }
39 |
40 | getModuleDirPath () {
41 | return path.join(__dirname, 'builders')
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/builder.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import path from 'path'
5 | import LogParser from './parsers/log-parser'
6 | import FdbParser from './parsers/fdb-parser'
7 | import { heredoc, isPdfFile, isPsFile, isDviFile } from './werkzeug.js'
8 |
9 | export default class Builder {
10 | envPathKey = this.getEnvironmentPathKey(process.platform)
11 |
12 | static canProcess (state) {}
13 | async run (jobState) {}
14 | constructArgs (jobState) {}
15 | async checkRuntimeDependencies () {}
16 |
17 | logStatusCode (statusCode, stderr) {
18 | switch (statusCode) {
19 | case 127:
20 | latex.log.error(heredoc(`
21 | TeXification failed! Builder executable '${this.executable}' not found.
22 | latex.texPath
23 | as configured: ${atom.config.get('latex.texPath')}
24 | when resolved: ${this.constructPath()}
25 | Make sure latex.texPath is configured correctly either adjust it \
26 | via the settings view, or directly in your config.cson file.
27 | `))
28 | break
29 | case 0:
30 | break
31 | default:
32 | const errorOutput = stderr ? ` and output of "${stderr}"` : ''
33 | latex.log.error(`TeXification failed with status code ${statusCode}${errorOutput}`)
34 | }
35 | }
36 |
37 | parseLogFile (jobState) {
38 | const logFilePath = this.resolveLogFilePath(jobState)
39 | if (fs.existsSync(logFilePath)) {
40 | let filePath = jobState.getTexFilePath()
41 | // Use main source path if the generated LaTeX file is missing. This will
42 | // enable log parsing and finding the project root to continue without the
43 | // generated LaTeX file.
44 | if (!filePath) filePath = jobState.getFilePath()
45 | const parser = this.getLogParser(logFilePath, filePath)
46 | const result = parser.parse()
47 | if (result) {
48 | if (result.messages) {
49 | jobState.setLogMessages(result.messages)
50 | }
51 | if (result.outputFilePath) {
52 | jobState.setOutputFilePath(result.outputFilePath)
53 | }
54 | }
55 | }
56 | }
57 |
58 | getLogParser (logFilePath, texFilePath) {
59 | return new LogParser(logFilePath, texFilePath)
60 | }
61 |
62 | constructChildProcessOptions (directoryPath, defaultEnv) {
63 | const env = Object.assign(defaultEnv || {}, process.env)
64 | const childPath = this.constructPath()
65 | if (childPath) {
66 | env[this.envPathKey] = childPath
67 | }
68 |
69 | return {
70 | allowKill: true,
71 | encoding: 'utf8',
72 | maxBuffer: 52428800, // Set process' max buffer size to 50 MB.
73 | cwd: directoryPath, // Run process with sensible CWD.
74 | env
75 | }
76 | }
77 |
78 | constructPath () {
79 | let texPath = (atom.config.get('latex.texPath') || '').trim()
80 | if (texPath.length === 0) {
81 | texPath = this.defaultTexPath(process.platform)
82 | }
83 |
84 | const processPath = process.env[this.envPathKey]
85 | const match = texPath.match(/^(.*)(\$PATH)(.*)$/)
86 | if (match) {
87 | return `${match[1]}${processPath}${match[3]}`
88 | }
89 |
90 | return [texPath, processPath]
91 | .filter(str => str && str.length > 0)
92 | .join(path.delimiter)
93 | }
94 |
95 | defaultTexPath (platform) {
96 | if (platform === 'win32') {
97 | return [
98 | '%SystemDrive%\\texlive\\2017\\bin\\win32',
99 | '%SystemDrive%\\texlive\\2016\\bin\\win32',
100 | '%SystemDrive%\\texlive\\2015\\bin\\win32',
101 | '%ProgramFiles%\\MiKTeX 2.9\\miktex\\bin\\x64',
102 | '%ProgramFiles(x86)%\\MiKTeX 2.9\\miktex\\bin'
103 | ].join(';')
104 | }
105 |
106 | return [
107 | '/usr/texbin',
108 | '/Library/TeX/texbin'
109 | ].join(':')
110 | }
111 |
112 | resolveOutputFilePath (jobState, ext) {
113 | let { dir, name } = path.parse(jobState.getFilePath())
114 | if (jobState.getJobName()) {
115 | name = jobState.getJobName()
116 | }
117 | dir = path.resolve(dir, jobState.getOutputDirectory())
118 | return path.format({ dir, name, ext })
119 | }
120 |
121 | resolveLogFilePath (jobState) {
122 | return this.resolveOutputFilePath(jobState, '.log')
123 | }
124 |
125 | getEnvironmentPathKey (platform) {
126 | if (platform === 'win32') { return 'Path' }
127 | return 'PATH'
128 | }
129 |
130 | resolveFdbFilePath (jobState) {
131 | return this.resolveOutputFilePath(jobState, '.fdb_latexmk')
132 | }
133 |
134 | parseFdbFile (jobState) {
135 | const fdbFilePath = this.resolveFdbFilePath(jobState)
136 | if (fs.existsSync(fdbFilePath)) {
137 | const parser = this.getFdbParser(fdbFilePath)
138 | const result = parser.parse()
139 | if (result) {
140 | jobState.setFileDatabase(result)
141 | }
142 | }
143 | }
144 |
145 | getFdbParser (fdbFilePath) {
146 | return new FdbParser(fdbFilePath)
147 | }
148 |
149 | parseLogAndFdbFiles (jobState) {
150 | this.parseLogFile(jobState)
151 | this.parseFdbFile(jobState)
152 |
153 | const fdb = jobState.getFileDatabase()
154 | if (fdb) {
155 | const sections = ['ps2pdf', 'xdvipdfmx', 'dvipdf', 'dvips', 'latex', 'pdflatex', 'lualatex', 'xelatex']
156 | let output
157 |
158 | for (const section of sections) {
159 | if (fdb[section] && fdb[section].generated) {
160 | const generated = fdb[section].generated
161 |
162 | output = generated.find(output => isPdfFile(output))
163 | if (output) break
164 |
165 | output = generated.find(output => isPsFile(output))
166 | if (output) break
167 |
168 | output = generated.find(output => isDviFile(output))
169 | if (output) break
170 | }
171 | }
172 |
173 | if (output) {
174 | jobState.setOutputFilePath(path.resolve(jobState.getProjectPath(), path.normalize(output)))
175 | }
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/lib/builders/knitr.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import path from 'path'
4 | import Builder from '../builder'
5 |
6 | const MISSING_PACKAGE_PATTERN = /there is no package called [‘']([^’']+)[’']/g
7 | const OUTPUT_PATH_PATTERN = /\[\d+]\s+"(.*)"/
8 | const RSCRIPT_VERSION_PATTERN = /version\s+(\S+)/i
9 | const PACKAGE_VERSION_PATTERN = /^\[1] "([^"]*)"/
10 |
11 | function escapePath (filePath) {
12 | return filePath.replace(/\\/g, '\\\\')
13 | }
14 |
15 | export default class KnitrBuilder extends Builder {
16 | executable = 'Rscript'
17 |
18 | static canProcess (state) {
19 | return !state.getTexFilePath() && !!state.getKnitrFilePath()
20 | }
21 |
22 | async run (jobState) {
23 | const args = this.constructArgs(jobState)
24 | const { statusCode, stdout, stderr } = await this.execRscript(jobState.getProjectPath(), args, 'error')
25 | if (statusCode !== 0) {
26 | this.logStatusCode(statusCode, stderr)
27 | return statusCode
28 | }
29 |
30 | jobState.setTexFilePath(this.resolveOutputPath(jobState.getKnitrFilePath(), stdout))
31 |
32 | const builder = latex.builderRegistry.getBuilder(jobState)
33 | const code = await builder.run(jobState)
34 |
35 | if (code === 0 && jobState.getEnableSynctex()) {
36 | const args = this.constructPatchSynctexArgs(jobState)
37 | await this.execRscript(jobState.getProjectPath(), args, 'warning')
38 | }
39 |
40 | return code
41 | }
42 |
43 | async checkRuntimeDependencies () {
44 | const { statusCode, stderr } = await this.execRscript('.', ['--version'], 'warning')
45 |
46 | if (statusCode !== 0) {
47 | latex.log.warning(`Rscript check failed with code ${statusCode} and response of "${stderr}".`)
48 | return
49 | }
50 |
51 | const match = stderr.match(RSCRIPT_VERSION_PATTERN)
52 |
53 | if (!match) {
54 | latex.log.warning(`Rscript check succeeded but with an unknown version response of "${stderr}".`)
55 | return
56 | }
57 |
58 | const version = match[1]
59 |
60 | latex.log.info(`Rscript check succeeded. Found version ${version}.`)
61 |
62 | await this.checkRscriptPackageVersion('knitr')
63 | await this.checkRscriptPackageVersion('patchSynctex', '0.1-4')
64 | }
65 |
66 | async checkRscriptPackageVersion (packageName, minimumVersion) {
67 | const result = await this.execRscript('.', [`-e "installed.packages()['${packageName}','Version']"`], 'warning')
68 |
69 | if (result.statusCode === 0) {
70 | const match = result.stdout.match(PACKAGE_VERSION_PATTERN)
71 | if (match) {
72 | const version = match[1]
73 | const message = `Rscript ${packageName} package check succeeded. Found version ${version}.`
74 | if (minimumVersion && minimumVersion > version) {
75 | latex.log.warning(`${message} Minimum version ${minimumVersion} needed.`)
76 | } else {
77 | latex.log.info(message)
78 | }
79 | return
80 | }
81 | }
82 |
83 | latex.log.warning(`Rscript package ${packageName} was not found.`)
84 | }
85 |
86 | async execRscript (directoryPath, args, type) {
87 | const command = `${this.executable} ${args.join(' ')}`
88 | const options = this.constructChildProcessOptions(directoryPath)
89 |
90 | let { statusCode, stdout, stderr } = await latex.process.executeChildProcess(command, options)
91 |
92 | if (statusCode !== 0) {
93 | // Parse error message to detect missing libraries.
94 | let match
95 | while ((match = MISSING_PACKAGE_PATTERN.exec(stderr)) !== null) {
96 | const text = `The R package "${match[1]}" could not be loaded.`
97 | latex.log.showMessages([{ type, text }])
98 | statusCode = -1
99 | }
100 | }
101 |
102 | return { statusCode, stdout, stderr }
103 | }
104 |
105 | constructArgs (jobState) {
106 | const args = [
107 | '-e "library(knitr)"',
108 | '-e "opts_knit$set(concordance = TRUE)"',
109 | `-e "knit('${escapePath(jobState.getKnitrFilePath())}')"`
110 | ]
111 |
112 | return args
113 | }
114 |
115 | constructPatchSynctexArgs (jobState) {
116 | let synctexPath = this.resolveOutputFilePath(jobState, '')
117 |
118 | const args = [
119 | '-e "library(patchSynctex)"',
120 | `-e "patchSynctex('${escapePath(jobState.getKnitrFilePath())}',syncfile='${escapePath(synctexPath)}')"`
121 | ]
122 |
123 | return args
124 | }
125 |
126 | resolveOutputPath (sourcePath, stdout) {
127 | const candidatePath = OUTPUT_PATH_PATTERN.exec(stdout)[1]
128 | if (path.isAbsolute(candidatePath)) {
129 | return candidatePath
130 | }
131 |
132 | const sourceDir = path.dirname(sourcePath)
133 | return path.join(sourceDir, candidatePath)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/lib/builders/latexmk.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import path from 'path'
4 | import Builder from '../builder'
5 |
6 | const LATEX_PATTERN = /^latex|u?platex$/
7 | const LATEXMK_VERSION_PATTERN = /Version\s+(\S+)/i
8 | const LATEXMK_MINIMUM_VERSION = '4.37'
9 | const PDF_ENGINE_PATTERN = /^(xelatex|lualatex)$/
10 |
11 | export default class LatexmkBuilder extends Builder {
12 | executable = 'latexmk'
13 |
14 | static canProcess (state) {
15 | return !!state.getTexFilePath()
16 | }
17 |
18 | async run (jobState) {
19 | const args = this.constructArgs(jobState)
20 |
21 | const { statusCode, stderr } = await this.execLatexmk(jobState.getProjectPath(), args, 'error')
22 | if (statusCode !== 0) {
23 | this.logStatusCode(statusCode, stderr)
24 | }
25 |
26 | return statusCode
27 | }
28 |
29 | async execLatexmk (directoryPath, args, type) {
30 | const options = this.constructChildProcessOptions(directoryPath, { max_print_line: 1000 })
31 |
32 | if (atom.config.get('latex.useRelativePaths') && options.cwd) {
33 | const absPath = args[args.length - 1].slice(1, -1)
34 | const relPath = path.relative(options.cwd, absPath)
35 | args[args.length - 1] = `"${relPath}"`
36 | }
37 |
38 | const command = `${this.executable} ${args.join(' ')}`
39 |
40 | return latex.process.executeChildProcess(command, options)
41 | }
42 |
43 | async checkRuntimeDependencies () {
44 | const { statusCode, stdout, stderr } = await this.execLatexmk('.', ['-v'], 'error')
45 |
46 | if (statusCode !== 0) {
47 | latex.log.error(`latexmk check failed with code ${statusCode} and response of "${stderr}".`)
48 | return
49 | }
50 |
51 | const match = stdout.match(LATEXMK_VERSION_PATTERN)
52 |
53 | if (!match) {
54 | latex.log.warning(`latexmk check succeeded but with an unknown version response of "${stdout}".`)
55 | return
56 | }
57 |
58 | const version = match[1]
59 |
60 | if (version < LATEXMK_MINIMUM_VERSION) {
61 | latex.log.warning(`latexmk check succeeded but with a version of ${version}". Minimum version required is ${LATEXMK_MINIMUM_VERSION}.`)
62 | return
63 | }
64 |
65 | latex.log.info(`latexmk check succeeded. Found version ${version}.`)
66 | }
67 |
68 | logStatusCode (statusCode, stderr) {
69 | switch (statusCode) {
70 | case 10:
71 | latex.log.error('latexmk: Bad command line arguments.')
72 | break
73 | case 11:
74 | latex.log.error('latexmk: File specified on command line not found or other file not found.')
75 | break
76 | case 12:
77 | latex.log.error('latexmk: Failure in some part of making files.')
78 | break
79 | case 13:
80 | latex.log.error('latexmk: error in initialization file.')
81 | break
82 | case 20:
83 | latex.log.error('latexmk: probable bug or retcode from called program.')
84 | break
85 | default:
86 | super.logStatusCode(statusCode, stderr)
87 | }
88 | }
89 |
90 | constructArgs (jobState) {
91 | const args = [
92 | '-interaction=nonstopmode',
93 | '-f',
94 | '-cd',
95 | '-file-line-error'
96 | ]
97 |
98 | if (jobState.getShouldRebuild()) {
99 | args.push('-g')
100 | }
101 | if (jobState.getJobName()) {
102 | args.push(`-jobname="${jobState.getJobName()}"`)
103 | }
104 | if (jobState.getEnableShellEscape()) {
105 | args.push('-shell-escape')
106 | }
107 | if (jobState.getEnableSynctex()) {
108 | args.push('-synctex=1')
109 | }
110 | if (jobState.getEnableExtendedBuildMode()) {
111 | const latexmkrcPath = path.resolve(__dirname, '..', '..', 'resources', 'latexmkrc')
112 | args.push(`-r "${latexmkrcPath}"`)
113 | }
114 |
115 | if (jobState.getEngine().match(LATEX_PATTERN)) {
116 | args.push(`-latex="${jobState.getEngine()}"`)
117 | args.push(jobState.getOutputFormat() === 'pdf'
118 | ? this.constructPdfProducerArgs(jobState)
119 | : `-${jobState.getOutputFormat()}`)
120 | } else {
121 | // Look for other PDF engines that can be specified using short command
122 | // options, i.e. -lualatex and -xelatex
123 | if (jobState.getOutputFormat() === 'pdf' && jobState.getEngine().match(PDF_ENGINE_PATTERN)) {
124 | args.push(`-${jobState.getEngine()}`)
125 | } else {
126 | // Keep the option noise to a minimum by not passing default engine
127 | if (jobState.getEngine() !== 'pdflatex') {
128 | args.push(`-pdflatex="${jobState.getEngine()}"`)
129 | }
130 | args.push(`-${jobState.getOutputFormat()}`)
131 | }
132 | }
133 |
134 | if (jobState.getOutputDirectory()) {
135 | args.push(`-outdir="${jobState.getOutputDirectory()}"`)
136 | }
137 |
138 | args.push(`"${jobState.getTexFilePath()}"`)
139 | return args
140 | }
141 |
142 | constructPdfProducerArgs (jobState) {
143 | const producer = jobState.getProducer()
144 |
145 | switch (producer) {
146 | case 'ps2pdf':
147 | return '-pdfps'
148 | case 'dvipdf':
149 | return '-pdfdvi -e "$dvipdf = \'dvipdf %O %S %D\';"'
150 | default:
151 | return `-pdfdvi -e "$dvipdf = '${producer} %O -o %D %S';"`
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/lib/config-migrator.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import { shell } from 'electron'
4 | import _ from 'lodash'
5 | import { heredoc } from './werkzeug'
6 |
7 | export default function checkConfigAndMigrate () {
8 | // TODO: remove after grace period
9 | checkCleanExtensions()
10 | checkOpenerSetting()
11 | checkMasterFileSearchSetting()
12 | checkBuilder()
13 | }
14 |
15 | function checkBuilder () {
16 | const builder = atom.config.get('latex.builder')
17 | if (!builder) return
18 |
19 | atom.config.unset('latex.builder')
20 | if (builder !== 'texify') return
21 |
22 | // --------------------------------------------------
23 | // TODO: Remove this whole block after a grace period
24 | // --------------------------------------------------
25 | const message = `LaTeX: The texify builder has been deprecated`
26 | const description = heredoc(`
27 | Support for the \`texify\` builder has been deprecated in favor of \`latexmk\`,
28 | and has been removed.`)
29 |
30 | const title = 'How to use latexmk with MiKTeX'
31 | const url = 'https://github.com/thomasjo/atom-latex/wiki/Using-latexmk-with-MiKTeX'
32 | const openUrl = (event) => {
33 | // NOTE: Horrible hack due to a bug in atom/notifications module...
34 | const element = event.target.parentElement.parentElement.parentElement.parentElement
35 | const notification = element.getModel()
36 | notification.dismiss()
37 |
38 | shell.openExternal(url)
39 | }
40 |
41 | atom.notifications.addWarning(message, {
42 | dismissable: true, description, buttons: [{ text: title, onDidClick: openUrl }]
43 | })
44 | }
45 |
46 | function checkMasterFileSearchSetting () {
47 | if (!atom.config.get('latex.useMasterFileSearch')) return
48 |
49 | atom.config.unset('latex.useMasterFileSearch')
50 |
51 | const message = `LaTeX: The Master File Search setting has been deprecated`
52 | const description = heredoc(`
53 | Support for the Master File Search setting has been deprecated in favor of
54 | \`%!TEX root\` magic comments, and has been removed.`)
55 | atom.notifications.addInfo(message, { description })
56 | }
57 |
58 | function checkCleanExtensions () {
59 | const cleanExtensions = atom.config.get('latex.cleanExtensions')
60 | if (!cleanExtensions) return
61 |
62 | let cleanPatterns = atom.config.get('latex.cleanPatterns')
63 | const defaultExtensions = [
64 | '.aux', '.bbl', '.blg', '.fdb_latexmk', '.fls', '.lof', '.log',
65 | '.lol', '.lot', '.nav', '.out', '.pdf', '.snm', '.synctex.gz', '.toc'
66 | ]
67 |
68 | atom.config.unset('latex.cleanExtensions')
69 |
70 | const removedExtensions = _.difference(defaultExtensions, cleanExtensions)
71 | cleanPatterns = _.difference(cleanPatterns, removedExtensions.map(extension => `**/*${extension}`))
72 |
73 | const addedExtensions = _.difference(cleanExtensions, defaultExtensions)
74 | cleanPatterns = _.union(cleanPatterns, addedExtensions.map(extension => `**/*${extension}`))
75 |
76 | atom.config.set('latex.cleanPatterns', cleanPatterns)
77 | const message = 'LaTeX: The "latex:clean" command has changed'
78 | const description = heredoc(`
79 | Your custom extensions in the \`Clean Extensions\` settings have
80 | been migrated to the new setting \`Clean Patterns\`.`)
81 | atom.notifications.addInfo(message, { description })
82 | }
83 |
84 | function checkOpenerSetting () {
85 | const alwaysOpenResultInAtom = atom.config.get('latex.alwaysOpenResultInAtom')
86 | if (!alwaysOpenResultInAtom) return
87 |
88 | atom.config.unset('latex.alwaysOpenResultInAtom')
89 | atom.config.set('latex.opener', 'pdf-view')
90 | }
91 |
--------------------------------------------------------------------------------
/lib/latex.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Composer from './composer'
4 | import OpenerRegistry from './opener-registry'
5 | import ProcessManager from './process-manager'
6 | import StatusIndicator from './status-indicator'
7 | import BuilderRegistry from './builder-registry'
8 | import Logger from './logger'
9 | import { CompositeDisposable, Disposable } from 'atom'
10 |
11 | function defineImmutableProperty (obj, name, value) {
12 | if (Disposable.isDisposable(value)) {
13 | obj.disposables.add(value)
14 | }
15 | Object.defineProperty(obj, name, { value })
16 | }
17 |
18 | export default class Latex extends Disposable {
19 | disposables = new CompositeDisposable()
20 |
21 | constructor () {
22 | super(() => this.disposables.dispose())
23 |
24 | defineImmutableProperty(this, 'builderRegistry', new BuilderRegistry())
25 | defineImmutableProperty(this, 'composer', new Composer())
26 | defineImmutableProperty(this, 'log', new Logger())
27 | defineImmutableProperty(this, 'opener', new OpenerRegistry())
28 | defineImmutableProperty(this, 'process', new ProcessManager())
29 | defineImmutableProperty(this, 'status', new StatusIndicator())
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import _ from 'lodash'
4 | import { CompositeDisposable, Disposable, Emitter } from 'atom'
5 | import { getEditorDetails } from './werkzeug'
6 | import LogDock from './views/log-dock'
7 |
8 | export default class Logger extends Disposable {
9 | disposables = new CompositeDisposable()
10 | emitter = new Emitter()
11 |
12 | constructor () {
13 | super(() => this.disposables.dispose())
14 | this.loggingLevel = atom.config.get('latex.loggingLevel')
15 | this.disposables.add(atom.config.onDidChange('latex.loggingLevel', () => {
16 | this.loggingLevel = atom.config.get('latex.loggingLevel')
17 | this.refresh()
18 | }))
19 | this.disposables.add(this.emitter)
20 | this.disposables.add(atom.workspace.addOpener(uri => {
21 | if (uri === LogDock.LOG_DOCK_URI) {
22 | return new LogDock()
23 | }
24 | }))
25 |
26 | this.messages = []
27 | }
28 |
29 | onMessages (callback) {
30 | return this.emitter.on('messages', callback)
31 | }
32 |
33 | error (text, filePath, range, logPath, logRange) {
34 | this.showMessages([{ type: 'error', text, filePath, range, logPath, logRange }])
35 | }
36 |
37 | warning (text, filePath, range, logPath, logRange) {
38 | this.showMessages([{ type: 'warning', text, filePath, range, logPath, logRange }])
39 | }
40 |
41 | info (text, filePath, range, logPath, logRange) {
42 | this.showMessages([{ type: 'info', text, filePath, range, logPath, logRange }])
43 | }
44 |
45 | showMessages (messages) {
46 | messages = messages.map(message => _.pickBy(message))
47 | this.messages = this.messages.concat(messages)
48 |
49 | const filteredMessages = messages.filter(message => this.messageTypeIsVisible(message.type))
50 |
51 | if (filteredMessages.length > 0) {
52 | this.emitter.emit('messages', { messages: filteredMessages, reset: false })
53 | }
54 | }
55 |
56 | clear () {
57 | this.messages = []
58 | this.refresh()
59 | }
60 |
61 | refresh () {
62 | this.emitter.emit('messages', { messages: this.getMessages(), reset: true })
63 | }
64 |
65 | getMessages (useFilters = true) {
66 | return useFilters
67 | ? this.messages.filter(message => this.messageTypeIsVisible(message.type))
68 | : this.messages
69 | }
70 |
71 | setMessages (messages) {
72 | this.messages = messages
73 | this.emitter.emit('messages', { messages, reset: true })
74 | }
75 |
76 | messageTypeIsVisible (type) {
77 | return type === 'error' ||
78 | (this.loggingLevel !== 'error' && type === 'warning') ||
79 | (this.loggingLevel === 'info' && type === 'info')
80 | }
81 |
82 | async sync () {
83 | // FIXME: There should be no direct interaction with editors. The required
84 | // values should be arguments.
85 | const { filePath, position } = getEditorDetails()
86 | if (filePath) {
87 | const logDock = await this.show()
88 | if (logDock) {
89 | logDock.update({ filePath, position })
90 | }
91 | }
92 | }
93 |
94 | async toggle () {
95 | return atom.workspace.toggle(LogDock.LOG_DOCK_URI)
96 | }
97 |
98 | async show () {
99 | return atom.workspace.open(LogDock.LOG_DOCK_URI)
100 | }
101 |
102 | async hide () {
103 | return atom.workspace.hide(LogDock.LOG_DOCK_URI)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import { CompositeDisposable, Disposable } from 'atom'
4 |
5 | export default {
6 | activate (serialized) {
7 | this.bootstrap()
8 |
9 | if (serialized && serialized.messages) {
10 | latex.log.setMessages(serialized.messages)
11 | }
12 |
13 | this.disposables.add(atom.commands.add('atom-workspace', {
14 | 'latex:build': () => latex.composer.build(false),
15 | 'latex:check-runtime': () => this.checkRuntime(),
16 | 'latex:clean': () => latex.composer.clean(),
17 | 'latex:clear-log': () => latex.log.clear(),
18 | 'latex:hide-log': () => latex.log.hide(),
19 | 'latex:kill': () => latex.composer.kill(),
20 | 'latex:rebuild': () => latex.composer.build(true),
21 | 'latex:show-log': () => latex.log.show(),
22 | 'latex:sync-log': () => latex.log.sync(),
23 | 'latex:sync': () => latex.composer.sync(),
24 | 'latex:toggle-log': () => latex.log.toggle()
25 | }))
26 |
27 | this.disposables.add(atom.workspace.observeTextEditors(editor => {
28 | this.disposables.add(editor.onDidSave(() => {
29 | // Let's play it safe; only trigger builds for the active editor.
30 | const activeEditor = atom.workspace.getActiveTextEditor()
31 | if (editor === activeEditor && atom.config.get('latex.buildOnSave')) {
32 | latex.composer.build(false, false)
33 | }
34 | }))
35 | }))
36 |
37 | const MarkerManager = require('./marker-manager')
38 | this.disposables.add(atom.workspace.observeTextEditors(editor => {
39 | this.disposables.add(new MarkerManager(editor))
40 | }))
41 |
42 | if (!atom.inSpecMode()) {
43 | const checkConfigAndMigrate = require('./config-migrator')
44 | checkConfigAndMigrate()
45 | }
46 | },
47 |
48 | deactivate () {
49 | if (this.disposables) {
50 | this.disposables.dispose()
51 | delete this.disposables
52 | }
53 |
54 | delete global.latex
55 | },
56 |
57 | serialize () {
58 | return { messages: latex.log.getMessages(false) }
59 | },
60 |
61 | consumeStatusBar (statusBar) {
62 | this.bootstrap()
63 | latex.status.attachStatusBar(statusBar)
64 | return new Disposable(() => {
65 | if (global.latex) {
66 | global.latex.status.detachStatusBar()
67 | }
68 | })
69 | },
70 |
71 | deserializeLog (serialized) {
72 | this.bootstrap()
73 | const LogDock = require('./views/log-dock')
74 | return new LogDock()
75 | },
76 |
77 | bootstrap () {
78 | if (!this.disposables) {
79 | this.disposables = new CompositeDisposable()
80 | }
81 |
82 | if (global.latex) { return }
83 |
84 | const Latex = require('./latex')
85 | global.latex = new Latex()
86 | this.disposables.add(global.latex)
87 | },
88 |
89 | async checkRuntime () {
90 | // latex.log.group('LaTeX Check')
91 | latex.log.clear()
92 | await latex.builderRegistry.checkRuntimeDependencies()
93 | latex.opener.checkRuntimeDependencies()
94 | // latex.log.groupEnd()
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/marker-manager.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import { CompositeDisposable, Disposable } from 'atom'
4 |
5 | export default class MarkerManager extends Disposable {
6 | disposables = new CompositeDisposable()
7 |
8 | constructor (editor) {
9 | super(() => this.disposables.dispose())
10 |
11 | this.editor = editor
12 | this.markers = []
13 |
14 | this.disposables.add(latex.log.onMessages(({ messages, reset }) => this.addMarkers(messages, reset)))
15 | this.disposables.add(new Disposable(() => this.clear()))
16 | this.disposables.add(this.editor.onDidDestroy(() => this.dispose()))
17 | this.disposables.add(atom.config.onDidChange('latex.loggingLevel', () => this.update()))
18 |
19 | this.addMarkers(latex.log.getMessages())
20 | }
21 |
22 | update () {
23 | this.addMarkers(latex.log.getMessages(), true)
24 | }
25 |
26 | addMarkers (messages, reset) {
27 | if (reset) this.clear()
28 |
29 | const editorPath = this.editor.getPath()
30 | const isVisible = (filePath, range) => filePath && range && editorPath.includes(filePath)
31 |
32 | if (editorPath) {
33 | for (const message of messages) {
34 | if (isVisible(message.filePath, message.range)) {
35 | this.addMarker(message.type, message.filePath, message.range)
36 | }
37 | if (isVisible(message.logPath, message.logRange)) {
38 | this.addMarker(message.type, message.logPath, message.logRange)
39 | }
40 | }
41 | }
42 | }
43 |
44 | addMarker (type, filePath, range) {
45 | const marker = this.editor.markBufferRange(range, { invalidate: 'touch' })
46 | this.editor.decorateMarker(marker, { type: 'line-number', class: `latex-${type}` })
47 | this.markers.push(marker)
48 | }
49 |
50 | clear () {
51 | for (const marker of this.markers) {
52 | marker.destroy()
53 | }
54 | this.markers = []
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/opener-registry.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import _ from 'lodash'
4 | import path from 'path'
5 | import { CompositeDisposable, Disposable } from 'atom'
6 |
7 | export default class OpenerRegistry extends Disposable {
8 | openers = new Map()
9 | disposables = new CompositeDisposable()
10 |
11 | constructor () {
12 | super(() => this.disposables.dispose())
13 | this.initializeOpeners()
14 | }
15 |
16 | initializeOpeners () {
17 | const schema = atom.config.getSchema('latex.opener')
18 | const dir = path.join(__dirname, 'openers')
19 | const ext = '.js'
20 | for (const openerName of schema.enum) {
21 | if (openerName !== 'automatic') {
22 | const name = `${openerName}-opener`
23 | const OpenerImpl = require(path.format({ dir, name, ext }))
24 | const opener = new OpenerImpl()
25 | this.disposables.add(opener)
26 | this.openers.set(openerName, opener)
27 | }
28 | }
29 | }
30 |
31 | checkRuntimeDependencies () {
32 | const pdfOpeners = Array.from(this.getCandidateOpeners('foo.pdf').keys())
33 | if (pdfOpeners.length) {
34 | latex.log.info(`The following PDF capable openers were found: ${pdfOpeners.join(', ')}.`)
35 | } else {
36 | latex.log.error('No PDF capable openers were found.')
37 | }
38 |
39 | const psOpeners = Array.from(this.getCandidateOpeners('foo.ps').keys())
40 | if (psOpeners.length) {
41 | latex.log.info(`The following PS capable openers were found: ${psOpeners.join(', ')}.`)
42 | } else {
43 | latex.log.warning('No PS capable openers were found.')
44 | }
45 |
46 | const dviOpeners = Array.from(this.getCandidateOpeners('foo.dvi').keys())
47 | if (dviOpeners.length) {
48 | latex.log.info(`The following DVI capable openers were found: ${dviOpeners.join(', ')}.`)
49 | } else {
50 | latex.log.warning('No DVI capable openers were found.')
51 | }
52 | }
53 |
54 | async open (filePath, texPath, lineNumber) {
55 | const name = atom.config.get('latex.opener')
56 | let opener = this.openers.get(name)
57 |
58 | if (!opener || !opener.canOpen(filePath)) {
59 | opener = this.findOpener(filePath)
60 | }
61 |
62 | if (opener) {
63 | return opener.open(filePath, texPath, lineNumber)
64 | } else {
65 | latex.log.warning(`No opener found that can open ${filePath}.`)
66 | }
67 | }
68 |
69 | getCandidateOpeners (filePath) {
70 | const candidates = new Map()
71 | for (const [name, opener] of this.openers.entries()) {
72 | if (opener.canOpen(filePath)) candidates.set(name, opener)
73 | }
74 | return candidates
75 | }
76 |
77 | findOpener (filePath) {
78 | const openResultInBackground = atom.config.get('latex.openResultInBackground')
79 | const enableSynctex = atom.config.get('latex.enableSynctex')
80 | const candidates = Array.from(this.getCandidateOpeners(filePath).values())
81 |
82 | if (!candidates.length) return
83 |
84 | const rankedCandidates = _.orderBy(candidates,
85 | [opener => opener.hasSynctex(), opener => opener.canOpenInBackground()],
86 | ['desc', 'desc'])
87 |
88 | if (enableSynctex) {
89 | // If the user wants openResultInBackground also and there is an opener
90 | // that supports that and SyncTeX it will be the first one because of
91 | // the priority sort.
92 | const opener = rankedCandidates.find(opener => opener.hasSynctex())
93 | if (opener) return opener
94 | }
95 |
96 | if (openResultInBackground) {
97 | const opener = rankedCandidates.find(opener => opener.canOpenInBackground())
98 | if (opener) return opener
99 | }
100 |
101 | return rankedCandidates[0]
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/lib/opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import { Disposable } from 'atom'
4 |
5 | export default class Opener extends Disposable {
6 | async open (filePath, texPath, lineNumber) {}
7 |
8 | shouldOpenInBackground () {
9 | return atom.config.get('latex.openResultInBackground')
10 | }
11 |
12 | canOpen (filePath) {
13 | return false
14 | }
15 |
16 | hasSynctex () {
17 | return false
18 | }
19 |
20 | canOpenInBackground () {
21 | return false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/openers/atril-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import EvinceOpener from './evince-opener'
4 |
5 | const DBUS_NAMES = {
6 | applicationObject: '/org/mate/atril/Atril',
7 | applicationInterface: 'org.mate.atril.Application',
8 |
9 | daemonService: 'org.mate.atril.Daemon',
10 | daemonObject: '/org/mate/atril/Daemon',
11 | daemonInterface: 'org.mate.atril.Daemon',
12 |
13 | windowInterface: 'org.mate.atril.Window'
14 | }
15 |
16 | export default class AtrilOpener extends EvinceOpener {
17 | constructor () {
18 | super('Atril', DBUS_NAMES)
19 | }
20 |
21 | canOpenInBackground () {
22 | return false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/openers/custom-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import Opener from '../opener'
5 | import { isPdfFile } from '../werkzeug'
6 |
7 | export default class CustomOpener extends Opener {
8 | // Custom PDF viewer does not support texPath and lineNumber.
9 | async open (filePath, texPath, lineNumber) {
10 | const command = `"${atom.config.get('latex.viewerPath')}" "${filePath}"`
11 |
12 | await latex.process.executeChildProcess(command, { showError: true })
13 | }
14 |
15 | canOpen (filePath) {
16 | return isPdfFile(filePath) && fs.existsSync(atom.config.get('latex.viewerPath'))
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/openers/evince-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Opener from '../opener'
4 | import { pathToUri, uriToPath } from '../werkzeug'
5 |
6 | const DBUS_NAMES = {
7 | applicationObject: '/org/gnome/evince/Evince',
8 | applicationInterface: 'org.gnome.evince.Application',
9 |
10 | daemonService: 'org.gnome.evince.Daemon',
11 | daemonObject: '/org/gnome/evince/Daemon',
12 | daemonInterface: 'org.gnome.evince.Daemon',
13 |
14 | windowInterface: 'org.gnome.evince.Window',
15 |
16 | fdApplicationObject: '/org/gtk/Application/anonymous',
17 | fdApplicationInterface: 'org.freedesktop.Application'
18 | }
19 |
20 | function syncSource (uri, point) {
21 | const filePath = uriToPath(uri)
22 | atom.focus()
23 | atom.workspace.open(filePath).then(editor => editor.setCursorBufferPosition(point))
24 | }
25 |
26 | export default class EvinceOpener extends Opener {
27 | windows = new Map()
28 |
29 | constructor (name = 'Evince', dbusNames = DBUS_NAMES) {
30 | super(() => {
31 | for (const filePath of Array.from(this.windows.keys())) {
32 | this.disposeWindow(filePath)
33 | }
34 | })
35 | this.name = name
36 | this.dbusNames = dbusNames
37 | this.initialize()
38 | }
39 |
40 | async initialize () {
41 | try {
42 | if (process.platform === 'linux') {
43 | const dbus = require('dbus-native')
44 | this.bus = dbus.sessionBus()
45 | this.daemon = await this.getInterface(this.dbusNames.daemonService, this.dbusNames.daemonObject, this.dbusNames.daemonInterface)
46 | }
47 | } catch (e) {}
48 | }
49 |
50 | async getWindow (filePath, texPath) {
51 | if (this.windows.has(filePath)) {
52 | return this.windows.get(filePath)
53 | }
54 |
55 | // First find the internal document name
56 | const documentName = await this.findDocument(filePath)
57 |
58 | // Get the application interface and get the window list of the application
59 | const evinceApplication = await this.getInterface(documentName, this.dbusNames.applicationObject, this.dbusNames.applicationInterface)
60 | const windowNames = await this.getWindowList(evinceApplication)
61 |
62 | // Get the window interface of the of the first (only) window
63 | const onClosed = () => this.disposeWindow(filePath)
64 | const windowInstance = {
65 | evinceWindow: await this.getInterface(documentName, windowNames[0], this.dbusNames.windowInterface),
66 | onClosed
67 | }
68 |
69 | if (this.dbusNames.fdApplicationObject) {
70 | // Get the GTK/FreeDesktop application interface so we can activate the window
71 | windowInstance.fdApplication = await this.getInterface(documentName, this.dbusNames.fdApplicationObject, this.dbusNames.fdApplicationInterface)
72 | }
73 |
74 | windowInstance.evinceWindow.on('SyncSource', syncSource)
75 | windowInstance.evinceWindow.on('Closed', windowInstance.onClosed)
76 | this.windows.set(filePath, windowInstance)
77 |
78 | // This seems to help with future syncs
79 | await this.syncView(windowInstance.evinceWindow, texPath, [0, 0], 0)
80 |
81 | return windowInstance
82 | }
83 |
84 | disposeWindow (filePath) {
85 | const windowInstance = this.windows.get(filePath)
86 | if (windowInstance) {
87 | windowInstance.evinceWindow.removeListener('SyncSource', syncSource)
88 | windowInstance.evinceWindow.removeListener('Closed', windowInstance.onClosed)
89 | this.windows.delete(filePath)
90 | }
91 | }
92 |
93 | async open (filePath, texPath, lineNumber) {
94 | try {
95 | const windowInstance = await this.getWindow(filePath, texPath)
96 | if (!this.shouldOpenInBackground() && windowInstance.fdApplication) {
97 | windowInstance.fdApplication.Activate({})
98 | }
99 |
100 | // SyncView seems to want to activate the window sometimes
101 | await this.syncView(windowInstance.evinceWindow, texPath, [lineNumber, 0], 0)
102 |
103 | return true
104 | } catch (error) {
105 | latex.log.error(`An error occured while trying to run ${this.name} opener`)
106 | return false
107 | }
108 | }
109 |
110 | canOpen (filePath) {
111 | return !!this.daemon
112 | }
113 |
114 | hasSynctex () {
115 | return true
116 | }
117 |
118 | canOpenInBackground () {
119 | return true
120 | }
121 |
122 | getInterface (serviceName, objectPath, interfaceName) {
123 | return new Promise((resolve, reject) => {
124 | this.bus.getInterface(serviceName, objectPath, interfaceName, (error, interfaceInstance) => {
125 | if (error) {
126 | reject(error)
127 | } else {
128 | resolve(interfaceInstance)
129 | }
130 | })
131 | })
132 | }
133 |
134 | getWindowList (evinceApplication) {
135 | return new Promise((resolve, reject) => {
136 | evinceApplication.GetWindowList((error, windowNames) => {
137 | if (error) {
138 | reject(error)
139 | } else {
140 | resolve(windowNames)
141 | }
142 | })
143 | })
144 | }
145 |
146 | syncView (evinceWindow, source, point, timestamp) {
147 | return new Promise((resolve, reject) => {
148 | evinceWindow.SyncView(source, point, timestamp, (error) => {
149 | if (error) {
150 | reject(error)
151 | } else {
152 | resolve()
153 | }
154 | })
155 | })
156 | }
157 |
158 | findDocument (filePath) {
159 | return new Promise((resolve, reject) => {
160 | const uri = pathToUri(filePath)
161 |
162 | this.daemon.FindDocument(uri, true, (error, documentName) => {
163 | if (error) {
164 | reject(error)
165 | } else {
166 | resolve(documentName)
167 | }
168 | })
169 | })
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/lib/openers/okular-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import { pathToUri } from '../werkzeug'
5 | import Opener from '../opener'
6 |
7 | export default class OkularOpener extends Opener {
8 | async open (filePath, texPath, lineNumber) {
9 | const okularPath = atom.config.get('latex.okularPath')
10 | const uri = pathToUri(filePath, `src:${lineNumber} ${texPath}`)
11 | const args = [
12 | '--unique',
13 | `"${uri}"`
14 | ]
15 | if (this.shouldOpenInBackground()) args.unshift('--noraise')
16 |
17 | const command = `"${okularPath}" ${args.join(' ')}`
18 |
19 | await latex.process.executeChildProcess(command, { showError: true })
20 | }
21 |
22 | canOpen (filePath) {
23 | return process.platform === 'linux' && fs.existsSync(atom.config.get('latex.okularPath'))
24 | }
25 |
26 | hasSynctex () {
27 | return true
28 | }
29 |
30 | canOpenInBackground () {
31 | return true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/openers/pdf-view-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Opener from '../opener'
4 | import { isPdfFile } from '../werkzeug'
5 |
6 | export default class PdfViewOpener extends Opener {
7 | async open (filePath, texPath, lineNumber) {
8 | const texPane = atom.workspace.paneForURI(texPath)
9 | const previousActivePane = atom.workspace.getActivePane()
10 |
11 | // This prevents splitting the right pane multiple times
12 | if (texPane) {
13 | texPane.activate()
14 | }
15 |
16 | const options = {
17 | searchAllPanes: true,
18 | split: atom.config.get('latex.pdfViewSplitDirection')
19 | }
20 |
21 | const item = await atom.workspace.open(filePath, options)
22 | if (item && item.forwardSync) {
23 | item.forwardSync(texPath, lineNumber)
24 | }
25 |
26 | if (previousActivePane && this.shouldOpenInBackground()) {
27 | previousActivePane.activate()
28 | }
29 |
30 | return true
31 | }
32 |
33 | canOpen (filePath) {
34 | return isPdfFile(filePath) && atom.packages.isPackageActive('pdf-view')
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/openers/preview-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Opener from '../opener'
4 | import { isPdfFile, isPsFile } from '../werkzeug'
5 |
6 | export default class PreviewOpener extends Opener {
7 | async open (filePath, texPath, lineNumber) {
8 | let command = `open -g -a Preview.app "${filePath}"`
9 | if (!this.shouldOpenInBackground()) {
10 | command = command.replace(/-g\s/, '')
11 | }
12 |
13 | await latex.process.executeChildProcess(command, { showError: true })
14 | }
15 |
16 | canOpen (filePath) {
17 | return process.platform === 'darwin' && (isPdfFile(filePath) || isPsFile(filePath))
18 | }
19 |
20 | canOpenInBackground () {
21 | return true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/openers/qpdfview-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import Opener from '../opener'
5 | import { isPdfFile, isPsFile } from '../werkzeug'
6 |
7 | export default class QpdfviewOpener extends Opener {
8 | async open (filePath, texPath, lineNumber) {
9 | const qpdfviewPath = atom.config.get('latex.qpdfviewPath')
10 | const args = [
11 | `--unique`,
12 | `"${filePath}"#src:"${texPath}":${lineNumber}:0`
13 | ]
14 | const command = `"${qpdfviewPath}" ${args.join(' ')}`
15 | await latex.process.executeChildProcess(command, { showError: true })
16 | }
17 |
18 | canOpen (filePath) {
19 | return process.platform === 'linux' &&
20 | (isPdfFile(filePath) || isPsFile(filePath)) &&
21 | fs.existsSync(atom.config.get('latex.qpdfviewPath'))
22 | }
23 |
24 | hasSynctex () {
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/openers/shell-open-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Opener from '../opener'
4 |
5 | export default class ShellOpenOpener extends Opener {
6 | // shell open does not support texPath and lineNumber.
7 | async open (filePath, texPath, lineNumber) {
8 | const command = `"${filePath}"`
9 |
10 | await latex.process.executeChildProcess(command, { showError: true })
11 | }
12 |
13 | canOpen (filePath) {
14 | return process.platform === 'win32'
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/openers/skim-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import { heredoc } from '../werkzeug'
5 | import Opener from '../opener'
6 |
7 | export default class SkimOpener extends Opener {
8 | async open (filePath, texPath, lineNumber) {
9 | const skimPath = atom.config.get('latex.skimPath')
10 | const shouldActivate = !this.shouldOpenInBackground()
11 | const command = heredoc(`
12 | osascript -e \
13 | "
14 | set theLine to \\"${lineNumber}\\" as integer
15 | set theFile to POSIX file \\"${filePath}\\"
16 | set theSource to POSIX file \\"${texPath}\\"
17 | set thePath to POSIX path of (theFile as alias)
18 | tell application \\"${skimPath}\\"
19 | if ${shouldActivate} then activate
20 | try
21 | set theDocs to get documents whose path is thePath
22 | if (count of theDocs) > 0 then revert theDocs
23 | end try
24 | open theFile
25 | tell front document to go to TeX line theLine from theSource
26 | end tell
27 | "
28 | `)
29 |
30 | await latex.process.executeChildProcess(command, { showError: true })
31 | }
32 |
33 | canOpen (filePath) {
34 | return process.platform === 'darwin' && fs.existsSync(atom.config.get('latex.skimPath'))
35 | }
36 |
37 | hasSynctex () {
38 | return true
39 | }
40 |
41 | canOpenInBackground () {
42 | return true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/openers/sumatra-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import Opener from '../opener'
5 | import { isPdfFile } from '../werkzeug'
6 |
7 | export default class SumatraOpener extends Opener {
8 | async open (filePath, texPath, lineNumber) {
9 | const sumatraPath = `"${atom.config.get('latex.sumatraPath')}"`
10 | const atomPath = process.argv[0]
11 | const args = [
12 | '-reuse-instance',
13 | '-forward-search',
14 | `"${texPath}"`,
15 | lineNumber,
16 | '-inverse-search',
17 | `"\\"${atomPath}\\" \\"%f:%l\\""`,
18 | `"${filePath}"`
19 | ]
20 |
21 | const command = `${sumatraPath} ${args.join(' ')}`
22 |
23 | await latex.process.executeChildProcess(command)
24 | }
25 |
26 | canOpen (filePath) {
27 | return process.platform === 'win32' && isPdfFile(filePath) &&
28 | fs.existsSync(atom.config.get('latex.sumatraPath'))
29 | }
30 |
31 | hasSynctex () {
32 | return true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/openers/x-reader-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import EvinceOpener from './evince-opener'
4 |
5 | const DBUS_NAMES = {
6 | applicationObject: '/org/x/reader/Xreader',
7 | applicationInterface: 'org.x.reader.Application',
8 |
9 | daemonService: 'org.x.reader.Daemon',
10 | daemonObject: '/org/x/reader/Daemon',
11 | daemonInterface: 'org.x.reader.Daemon',
12 |
13 | windowInterface: 'org.x.reader.Window'
14 | }
15 |
16 | export default class XReaderOpener extends EvinceOpener {
17 | constructor () {
18 | super('Xreader', DBUS_NAMES)
19 | }
20 |
21 | canOpenInBackground () {
22 | return false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/openers/xdg-open-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Opener from '../opener'
4 |
5 | export default class XdgOpenOpener extends Opener {
6 | // xdg-open does not support texPath and lineNumber.
7 | async open (filePath, texPath, lineNumber) {
8 | const command = `xdg-open "${filePath}"`
9 |
10 | await latex.process.executeChildProcess(command, { showError: true })
11 | }
12 |
13 | canOpen (filePath) {
14 | return process.platform === 'linux'
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/openers/zathura-opener.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import Opener from '../opener'
5 | import { isPdfFile, isPsFile } from '../werkzeug'
6 |
7 | export default class ZathuraOpener extends Opener {
8 | async open (filePath, texPath, lineNumber) {
9 | const zathuraPath = atom.config.get('latex.zathuraPath')
10 | const atomPath = process.argv[0]
11 | const args = [
12 | `--synctex-editor-command="\\"${atomPath}\\" \\"%{input}:%{line}\\""`,
13 | `--synctex-forward="${lineNumber}:1:${texPath}"`,
14 | `"${filePath}"`
15 | ]
16 | const command = `"${zathuraPath}" ${args.join(' ')}`
17 | await latex.process.executeChildProcess(command, { showError: true })
18 | }
19 |
20 | canOpen (filePath) {
21 | return process.platform === 'linux' &&
22 | (isPdfFile(filePath) || isPsFile(filePath)) &&
23 | fs.existsSync(atom.config.get('latex.zathuraPath'))
24 | }
25 |
26 | hasSynctex () {
27 | return true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/parser.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 |
5 | export default class Parser {
6 | constructor (filePath) {
7 | this.filePath = filePath
8 | }
9 |
10 | parse () {}
11 |
12 | getLines (defaultLines) {
13 | if (!fs.existsSync(this.filePath)) {
14 | if (defaultLines) return defaultLines
15 | throw new Error(`No such file: ${this.filePath}`)
16 | }
17 |
18 | const rawFile = fs.readFileSync(this.filePath, {encoding: 'utf-8'})
19 | const lines = rawFile.replace(/(\r\n)|\r/g, '\n').split('\n')
20 | return lines
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/parsers/fdb-parser.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Parser from '../parser.js'
4 |
5 | const SECTION_PATTERN = /^\["([^"]+)"]/
6 | const GROUP_PATTERN = /^\s+\(([^)]+)\)/
7 | const FILE_PATTERN = /^\s+"([^"]*)"/
8 |
9 | export default class FdbParser extends Parser {
10 | parse () {
11 | let results = {}
12 | let section
13 | let group
14 |
15 | for (const line of this.getLines()) {
16 | const sectionMatch = line.match(SECTION_PATTERN)
17 | if (sectionMatch) {
18 | section = sectionMatch[1]
19 | results[section] = {}
20 | group = 'source'
21 | results[section][group] = []
22 | continue
23 | }
24 |
25 | if (!section) continue
26 |
27 | const groupMatch = line.match(GROUP_PATTERN)
28 | if (groupMatch) {
29 | group = groupMatch[1]
30 | if (!results[section][group]) {
31 | results[section][group] = []
32 | }
33 | continue
34 | }
35 |
36 | if (!group) continue
37 |
38 | const fileMatch = line.match(FILE_PATTERN)
39 | if (fileMatch) {
40 | results[section][group].push(fileMatch[1])
41 | }
42 | }
43 |
44 | return results
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/parsers/log-parser.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Parser from '../parser.js'
4 | import path from 'path'
5 |
6 | /* eslint-disable no-multi-spaces */
7 |
8 | const OUTPUT_PATTERN = new RegExp('' +
9 | '^Output\\swritten\\son\\s' + // Leading text.
10 | '(.*)' + // Output path.
11 | '\\s\\(.*\\)\\.$' // Trailing text.
12 | )
13 |
14 | // Error pattern
15 | const ERROR_PATTERN = new RegExp('' +
16 | '^(?:(.*):(\\d+):|!)' + // File path and line number
17 | '(?: (.+) Error:)? ' + // Error type
18 | '(.+?)\\.?$' // Message text, the ending period is optional for MiKTeX
19 | )
20 |
21 | // Pattern for overfull/underfull boxes
22 | const BOX_PATTERN = new RegExp('' +
23 | '^((?:Over|Under)full \\\\[vh]box \\([^)]*\\))' + // Message text
24 | ' in paragraph at lines (\\d+)--(\\d+)$' // Line range
25 | )
26 |
27 | // Warning and Info pattern
28 | const WARNING_INFO_PATTERN = new RegExp('' +
29 | '^((?:(?:Class|Package) \\S+)|LaTeX|LaTeX Font) ' + // Message origin
30 | '(Warning|Info):\\s+' + // Message type
31 | '(.*?)' + // Message text
32 | '(?: on input line (\\d+))?\\.$' // Line number
33 | )
34 |
35 | /* eslint-enable no-multi-spaces */
36 |
37 | // Pattern for font messages that overflow onto the next line. We do not capture
38 | // anything from the match, but we need to know where the error message is
39 | // located in the log file.
40 | const INCOMPLETE_FONT_PATTERN = /^LaTeX Font .*[^.]$/
41 |
42 | // Pattern for \input markers which are surrounded by parentheses.
43 | const INPUT_FILE_PATTERN = /(\([^()[]+|\))/g
44 |
45 | // Pattern to remove leading and trailing spaces, quotes and left parenthesis.
46 | const INPUT_FILE_TRIM_PATTERN = /(^\([\s"]*|[\s"]+$)/g
47 |
48 | export default class LogParser extends Parser {
49 | constructor (filePath, texFilePath) {
50 | super(filePath)
51 | this.texFilePath = texFilePath
52 | this.projectPath = path.dirname(texFilePath)
53 | }
54 |
55 | parse () {
56 | const result = {
57 | logFilePath: this.filePath,
58 | outputFilePath: null,
59 | messages: []
60 | }
61 | const sourcePaths = [this.texFilePath]
62 |
63 | const lines = this.getLines()
64 | lines.forEach((line, index) => {
65 | // Ignore the first line because it has some confusing patterns
66 | if (index === 0) return
67 |
68 | // Simplest Thing That Works™ and KISS®
69 | const logRange = [[index, 0], [index, line.length]]
70 | let match = line.match(OUTPUT_PATTERN)
71 | if (match) {
72 | const filePath = match[1].replace(/"/g, '') // TODO: Fix with improved regex.
73 | result.outputFilePath = path.resolve(this.projectPath, filePath)
74 | return
75 | }
76 |
77 | match = line.match(ERROR_PATTERN)
78 | if (match) {
79 | const lineNumber = match[2] ? parseInt(match[2], 10) : undefined
80 | result.messages.push({
81 | type: 'error',
82 | text: (match[3] && match[3] !== 'LaTeX') ? match[3] + ': ' + match[4] : match[4],
83 | filePath: match[1] ? path.resolve(this.projectPath, match[1]) : sourcePaths[0],
84 | range: lineNumber ? [[lineNumber - 1, 0], [lineNumber - 1, Number.MAX_SAFE_INTEGER]] : undefined,
85 | logPath: this.filePath,
86 | logRange: logRange
87 | })
88 | return
89 | }
90 |
91 | match = line.match(BOX_PATTERN)
92 | if (match) {
93 | result.messages.push({
94 | type: 'warning',
95 | text: match[1],
96 | filePath: sourcePaths[0],
97 | range: [[parseInt(match[2], 10) - 1, 0], [parseInt(match[3], 10) - 1, Number.MAX_SAFE_INTEGER]],
98 | logPath: this.filePath,
99 | logRange: logRange
100 | })
101 | return
102 | }
103 |
104 | match = (INCOMPLETE_FONT_PATTERN.test(line) ? line + lines[index + 1].substring(15) : line).match(WARNING_INFO_PATTERN)
105 | if (match) {
106 | const lineNumber = match[4] ? parseInt(match[4], 10) : undefined
107 | result.messages.push({
108 | type: match[2].toLowerCase(),
109 | text: ((match[1] !== 'LaTeX') ? match[1] + ': ' + match[3] : match[3]).replace(/\s+/g, ' '),
110 | filePath: sourcePaths[0],
111 | range: lineNumber ? [[lineNumber - 1, 0], [lineNumber - 1, Number.MAX_SAFE_INTEGER]] : undefined,
112 | logPath: this.filePath,
113 | logRange: logRange
114 | })
115 | }
116 |
117 | // Keep a stack of source paths indicated by input parentheses. We may
118 | // capture phrases that are enclosed in parathesis that are not paths, but
119 | // this should ignored safely since the closing paratheses will pop the
120 | // path right back off of the source path stack.
121 | match = line.match(INPUT_FILE_PATTERN)
122 | if (match) {
123 | for (const token of match) {
124 | if (token === ')') {
125 | // Avoid popping texFilePath off of the stack.
126 | if (sourcePaths.length > 1) sourcePaths.shift()
127 | } else {
128 | sourcePaths.unshift(path.resolve(this.projectPath, token.replace(INPUT_FILE_TRIM_PATTERN, '')))
129 | }
130 | }
131 | }
132 | })
133 |
134 | return result
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/lib/parsers/magic-parser.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Parser from '../parser.js'
4 |
5 | /* eslint-disable no-multi-spaces */
6 |
7 | const MAGIC_COMMENT_PATTERN = new RegExp('' +
8 | '^%\\s*' + // Optional whitespace.
9 | '!T[Ee]X' + // Magic marker.
10 | '\\s+' + // Semi-optional whitespace.
11 | '(\\w+)' + // [1] Captures the magic keyword. E.g. 'root'.
12 | '\\s*=\\s*' + // Equal sign wrapped in optional whitespace.
13 | '(.*)' + // [2] Captures everything following the equal sign.
14 | '$' // EOL.
15 | )
16 |
17 | const LATEX_COMMAND_PATTERN = new RegExp('' +
18 | '\\' + // starting command \
19 | '\\w+' + // command name e.g. input
20 | '(\\{|\\w|\\}|/|\\]|\\[)*' // options to the command
21 | )
22 |
23 | /* eslint-enable no-multi-spaces */
24 |
25 | export default class MagicParser extends Parser {
26 | parse () {
27 | const result = {}
28 | const lines = this.getLines([])
29 | for (const line of lines) {
30 | const latexCommandMatch = line.match(LATEX_COMMAND_PATTERN)
31 | if (latexCommandMatch) { break } // Stop parsing if a latex command was found
32 |
33 | const match = line.match(MAGIC_COMMENT_PATTERN)
34 | if (match != null) {
35 | result[match[1]] = match[2].trim()
36 | }
37 | }
38 |
39 | return result
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/process-manager.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import childProcess from 'child_process'
4 | import kill from 'tree-kill'
5 | import { Disposable } from 'atom'
6 |
7 | export default class ProcessManager extends Disposable {
8 | processes = new Set()
9 |
10 | constructor () {
11 | super(() => this.killChildProcesses())
12 | }
13 |
14 | executeChildProcess (command, options = {}) {
15 | const { allowKill, showError, ...execOptions } = options
16 | return new Promise(resolve => {
17 | // Windows does not like \$ appearing in command lines so only escape
18 | // if we need to.
19 | if (process.platform !== 'win32') command = command.replace('$', '\\$')
20 | const { pid } = childProcess.exec(command, execOptions, (error, stdout, stderr) => {
21 | if (allowKill) {
22 | this.processes.delete(pid)
23 | }
24 | if (error && showError && latex && latex.log) {
25 | latex.log.error(`An error occurred while trying to run "${command}" (${error.code}).`)
26 | }
27 | resolve({
28 | statusCode: error ? error.code : 0,
29 | stdout,
30 | stderr
31 | })
32 | })
33 | if (allowKill) {
34 | this.processes.add(pid)
35 | }
36 | })
37 | }
38 |
39 | killChildProcesses () {
40 | for (const pid of this.processes.values()) {
41 | kill(pid)
42 | }
43 | this.processes.clear()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/status-indicator.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import StatusLabel from './views/status-label'
4 | import { Disposable } from 'atom'
5 |
6 | export default class StatusIndicator extends Disposable {
7 | constructor () {
8 | super(() => this.detachStatusBar())
9 | }
10 |
11 | attachStatusBar (statusBar) {
12 | this.statusLabel = new StatusLabel()
13 | this.statusTile = statusBar.addLeftTile({
14 | item: this.statusLabel,
15 | priority: 9001
16 | })
17 | }
18 |
19 | detachStatusBar () {
20 | if (this.statusTile) {
21 | this.statusTile.destroy()
22 | this.statusTile = null
23 | }
24 | if (this.statusLabel) {
25 | this.statusLabel.destroy()
26 | this.statusLabel = null
27 | }
28 | }
29 |
30 | setBusy () {
31 | if (this.statusLabel) {
32 | this.statusLabel.update({ busy: true })
33 | }
34 | }
35 |
36 | setIdle () {
37 | if (this.statusLabel) {
38 | this.statusLabel.update({ busy: false })
39 | }
40 | }
41 |
42 | show (text, type, icon, spin, title, onClick) {
43 | if (this.statusLabel) {
44 | this.statusLabel.update({ text, type, icon, spin, title, onClick })
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/views/file-reference.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 | import path from 'path'
6 |
7 | export default class FileReference {
8 | constructor (properties = { type: 'error' }) {
9 | this.properties = properties
10 | etch.initialize(this)
11 | }
12 |
13 | async destroy () {
14 | await etch.destroy(this)
15 | }
16 |
17 | render () {
18 | const { file, range } = this.properties
19 |
20 | if (!file) return
21 |
22 | const endLineReference = (range && range[0][0] !== range[1][0]) ? `\u2013${range[1][0] + 1}` : ''
23 | const lineReference = range ? ` (${range[0][0] + 1}${endLineReference})` : ''
24 | const text = path.basename(file)
25 | const clickHandler = () => {
26 | atom.workspace.open(file, { initialLine: range ? range[0][0] : 0 })
27 | }
28 |
29 | return (
30 |
31 | {text}
32 | {lineReference}
33 |
34 | )
35 | }
36 |
37 | update (properties) {
38 | this.properties = properties
39 | return etch.update(this)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/views/log-dock.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 | import { CompositeDisposable } from 'atom'
6 | import LogMessage from './log-message'
7 |
8 | export default class LogDock {
9 | static LOG_DOCK_URI = 'atom://latex/log'
10 |
11 | disposables = new CompositeDisposable()
12 |
13 | constructor (properties = {}) {
14 | this.properties = properties
15 | etch.initialize(this)
16 | this.disposables.add(latex.log.onMessages(() => this.update()))
17 | }
18 |
19 | async destroy () {
20 | this.disposables.dispose()
21 | await etch.destroy(this)
22 | }
23 |
24 | render () {
25 | let content = latex.log.getMessages().map(message => )
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | |
34 | Message |
35 | Source File |
36 | Log File |
37 |
38 |
39 | {content}
40 |
41 |
42 |
43 | )
44 | }
45 |
46 | update (properties = {}) {
47 | this.properties = properties
48 | return etch.update(this)
49 | }
50 |
51 | readAfterUpdate () {
52 | // Look for highlighted messages and scroll to them
53 | const highlighted = this.refs.body.getElementsByClassName('latex-highlight')
54 | if (highlighted.length) {
55 | highlighted[0].scrollIntoView()
56 | }
57 | }
58 |
59 | getTitle () {
60 | return 'LaTeX Log'
61 | }
62 |
63 | getURI () {
64 | return LogDock.LOG_DOCK_URI
65 | }
66 |
67 | getDefaultLocation () {
68 | return 'bottom'
69 | }
70 |
71 | serialize () {
72 | return {
73 | deserializer: 'latex/log'
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/views/log-message.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 | import { Range } from 'atom'
6 | import MessageIcon from './message-icon'
7 | import FileReference from './file-reference'
8 |
9 | export default class LogMessage {
10 | constructor (properties = {}) {
11 | this.properties = properties
12 | etch.initialize(this)
13 | }
14 |
15 | async destroy () {
16 | await etch.destroy(this)
17 | }
18 |
19 | render () {
20 | const message = this.properties.message
21 | const lines = message.text.split('\n').map(line => ({line}
))
22 |
23 | return (
24 |
25 | |
26 | {lines} |
27 | |
28 | |
29 |
30 | )
31 | }
32 |
33 | getClassNames (message) {
34 | const className = `latex-${message.type}`
35 |
36 | const matchesFilePath = message.filePath && this.properties.filePath === message.filePath
37 | const containsPosition = message.range && this.properties.position && Range.fromObject(message.range).containsPoint(this.properties.position)
38 | if (matchesFilePath && containsPosition) {
39 | return `${className} latex-highlight`
40 | }
41 |
42 | return className
43 | }
44 |
45 | update (properties) {
46 | this.properties = properties
47 | return etch.update(this)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/views/message-count.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 | import { CompositeDisposable } from 'atom'
6 | import MessageIcon from './message-icon'
7 |
8 | export default class MessageCount {
9 | disposables = new CompositeDisposable()
10 |
11 | constructor (properties = { type: 'error' }) {
12 | this.properties = properties
13 | etch.initialize(this)
14 | this.disposables.add(latex.log.onMessages(() => this.update()))
15 | }
16 |
17 | async destroy () {
18 | await etch.destroy(this)
19 | this.disposables.dispose()
20 | }
21 |
22 | render () {
23 | if (latex.log.messageTypeIsVisible(this.properties.type)) {
24 | const counts = latex.log.getMessages().reduce((total, message) => message.type === this.properties.type ? total + 1 : total, 0)
25 |
26 | return (
27 |
28 |
29 | {counts}
30 |
31 | )
32 | }
33 |
34 | return
35 | }
36 |
37 | update (properties = {}) {
38 | Object.assign(this.properties, properties)
39 | return etch.update(this)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/views/message-icon.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 |
6 | export default class MessageIcon {
7 | static icons = {
8 | error: 'stop',
9 | warning: 'alert',
10 | info: 'info'
11 | }
12 |
13 | constructor (properties = { type: 'error' }) {
14 | this.properties = properties
15 | etch.initialize(this)
16 | }
17 |
18 | async destroy () {
19 | await etch.destroy(this)
20 | }
21 |
22 | render () {
23 | return (
24 |
25 | )
26 | }
27 |
28 | update (properties) {
29 | this.properties = properties
30 | return etch.update(this)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/views/status-label.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 | /** @jsx etch.dom */
3 |
4 | import etch from 'etch'
5 | import MessageCount from './message-count'
6 |
7 | export default class StatusLabel {
8 | constructor (properties = {}) {
9 | this.properties = properties
10 | etch.initialize(this)
11 | }
12 |
13 | async destroy () {
14 | if (this.tooltip) {
15 | this.tooltip.dispose()
16 | }
17 | await etch.destroy(this)
18 | }
19 |
20 | render () {
21 | return (
22 | latex.log.show()}>
23 |
24 |
LaTeX
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | getClassNames () {
33 | const className = `latex-status inline-block`
34 |
35 | if (this.properties.busy) {
36 | return `${className} is-busy`
37 | }
38 |
39 | return className
40 | }
41 |
42 | update (properties = {}) {
43 | Object.assign(this.properties, properties)
44 | return etch.update(this)
45 | }
46 |
47 | readAfterUpdate () {
48 | if (this.tooltip) {
49 | this.tooltip.dispose()
50 | this.tooltip = null
51 | }
52 | this.tooltip = atom.tooltips.add(this.element, { title: 'Click to show LaTeX log' })
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/werkzeug.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import _ from 'lodash'
4 | import url from 'url'
5 |
6 | export default {
7 | pathToUri (filePath, hash) {
8 | if (process.platform === 'win32') {
9 | filePath = filePath.replace(/\\/g, '/')
10 | if (!filePath.startsWith('/')) {
11 | filePath = `/${filePath}`
12 | }
13 | }
14 |
15 | const urlObject = {
16 | protocol: 'file:',
17 | slashes: true,
18 | pathname: encodeURI(filePath).replace(/[?#]/g, encodeURIComponent)
19 | }
20 |
21 | if (hash) urlObject.hash = encodeURIComponent(hash)
22 |
23 | return url.format(urlObject)
24 | },
25 |
26 | uriToPath (uri) {
27 | let filePath = decodeURI(url.parse(uri).pathname || '')
28 |
29 | if (process.platform === 'win32') {
30 | filePath = filePath.replace(/\//g, '\\').replace(/^(.+)\|/, '$1:').replace(/\\([A-Z]:\\)/, '$1')
31 | } else if (!filePath.startsWith('/')) {
32 | filePath = `/${filePath}`
33 | }
34 |
35 | return filePath
36 | },
37 |
38 | heredoc (input) {
39 | if (input === null) { return null }
40 |
41 | const lines = _.dropWhile(input.split(/\r\n|\n|\r/), line => line.length === 0)
42 | const indentLength = _.takeWhile(lines[0], char => char === ' ').length
43 | const truncatedLines = lines.map(line => line.slice(indentLength))
44 |
45 | return truncatedLines.join('\n')
46 | },
47 |
48 | promisify (target) {
49 | return (...args) => {
50 | return new Promise((resolve, reject) => {
51 | target(...args, (error, data) => { error ? reject(error) : resolve(data) })
52 | })
53 | }
54 | },
55 |
56 | getEditorDetails () {
57 | const editor = atom.workspace.getActiveTextEditor()
58 | if (!editor) return {}
59 |
60 | const filePath = editor.getPath()
61 | const position = editor.getCursorBufferPosition()
62 | const lineNumber = position.row + 1
63 |
64 | return { editor, filePath, position, lineNumber }
65 | },
66 |
67 | replacePropertiesInString (text, properties) {
68 | return _.reduce(properties, (current, value, name) => current.replace(`{${name}}`, value), text)
69 | },
70 |
71 | isSourceFile (filePath) {
72 | return filePath && !!filePath.match(/\.(?:tex|tikz|lhs|lagda|[prs]nw)$/i)
73 | },
74 |
75 | isTexFile (filePath) {
76 | return filePath && !!filePath.match(/\.(?:tex|lhs|lagda)$/i)
77 | },
78 |
79 | isKnitrFile (filePath) {
80 | return filePath && !!filePath.match(/\.[rs]nw$/i)
81 | },
82 |
83 | isPdfFile (filePath) {
84 | return filePath && !!filePath.match(/\.pdf$/i)
85 | },
86 |
87 | isPsFile (filePath) {
88 | return filePath && !!filePath.match(/\.ps$/i)
89 | },
90 |
91 | isDviFile (filePath) {
92 | return filePath && !!filePath.match(/\.(?:dvi|xdv)$/i)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/menus/latex.json:
--------------------------------------------------------------------------------
1 | {
2 | "menu": [
3 | {
4 | "label": "Packages",
5 | "submenu": [
6 | {
7 | "label": "LaTeX",
8 | "submenu": [
9 | { "label": "Build", "command": "latex:build" },
10 | { "label": "Rebuild", "command": "latex:rebuild" },
11 | { "label": "Clean", "command": "latex:clean" },
12 | { "label": "Kill Build", "command": "latex:kill" },
13 | { "type": "separator" },
14 | { "label": "Toggle Log", "command": "latex:toggle-log" },
15 | { "label": "Show Log", "command": "latex:show-log" },
16 | { "label": "Hide Log", "command": "latex:hide-log" },
17 | { "label": "Clear Log", "command": "latex:clear-log" },
18 | { "type": "separator" },
19 | { "label": "Check Runtime", "command": "latex:check-runtime" }
20 | ]
21 | }
22 | ]
23 | }
24 | ],
25 | "context-menu": {
26 | "atom-text-editor[data-grammar~=\"latex\"]": [
27 | { "label": "LaTeX Sync", "command": "latex:sync" },
28 | { "label": "LaTeX Sync Log", "command": "latex:sync-log" }
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "latex",
3 | "version": "0.50.2",
4 | "description": "Compile LaTeX documents from within Atom",
5 | "keywords": [
6 | "tex",
7 | "latex",
8 | "latexmk",
9 | "texlive",
10 | "miktex"
11 | ],
12 | "license": "MIT",
13 | "repository": "https://github.com/thomasjo/atom-latex",
14 | "author": "Thomas Johansen ",
15 | "contributors": [
16 | "Tarn Burton "
17 | ],
18 | "main": "./lib/main",
19 | "dependencies": {
20 | "@dicy/client": "^0.13.0",
21 | "dbus-native": "^0.4.0",
22 | "etch": "0.12.6",
23 | "fs-plus": "^3.0.0",
24 | "glob": "^7.1.1",
25 | "js-yaml": "^3.11.0",
26 | "lodash": "^4.17.4",
27 | "minimatch": "^3.0.4",
28 | "temp": "^0.8.3",
29 | "tree-kill": "^1.1.0",
30 | "wrench": "^1.5.9"
31 | },
32 | "devDependencies": {
33 | "babel-eslint": "^8.2.2",
34 | "snazzy": "^7.1.1",
35 | "standard": "^11.0.0"
36 | },
37 | "engines": {
38 | "atom": ">=1.19.0 <2.0.0"
39 | },
40 | "standard": {
41 | "parser": "babel-eslint",
42 | "globals": [
43 | "atom",
44 | "latex",
45 | "afterEach",
46 | "beforeEach",
47 | "describe",
48 | "expect",
49 | "it",
50 | "jasmine",
51 | "runs",
52 | "spyOn",
53 | "waitsFor",
54 | "waitsForPromise"
55 | ]
56 | },
57 | "activationCommands": {
58 | "atom-workspace": [
59 | "latex:build",
60 | "latex:check-runtime",
61 | "latex:clean",
62 | "latex:clear-log",
63 | "latex:hide-log",
64 | "latex:rebuild",
65 | "latex:show-log",
66 | "latex:sync-log",
67 | "latex:sync",
68 | "latex:toggle-log"
69 | ]
70 | },
71 | "activationHooks": [
72 | "language-latex:grammar-used",
73 | "language-tex:grammar-used",
74 | "language-latexsimple:grammar-used"
75 | ],
76 | "consumedServices": {
77 | "status-bar": {
78 | "versions": {
79 | "^1.0.0": "consumeStatusBar"
80 | }
81 | }
82 | },
83 | "deserializers": {
84 | "latex/log": "deserializeLog"
85 | },
86 | "configSchema": {
87 | "texPath": {
88 | "title": "TeX Path",
89 | "description": "The full path to your TeX distribution's bin directory. Supports `$PATH` substitution.",
90 | "type": "string",
91 | "default": "",
92 | "order": 1
93 | },
94 | "engine": {
95 | "description": "Select standard LaTeX engine",
96 | "type": "string",
97 | "enum": [
98 | "pdflatex",
99 | "lualatex",
100 | "platex",
101 | "uplatex",
102 | "xelatex"
103 | ],
104 | "default": "pdflatex",
105 | "order": 2
106 | },
107 | "customEngine": {
108 | "description": "Enter command for custom LaTeX engine. Overrides Engine.",
109 | "type": "string",
110 | "default": "",
111 | "order": 3
112 | },
113 | "enableShellEscape": {
114 | "type": "boolean",
115 | "default": false,
116 | "order": 4
117 | },
118 | "enableSynctex": {
119 | "title": "Enable SyncTeX",
120 | "type": "boolean",
121 | "default": true,
122 | "order": 5
123 | },
124 | "useDicy": {
125 | "title": "Use DiCy",
126 | "description": "Use the experimental javascript based builder [`DiCy`](https://yitzchak.github.io/dicy/) instead of `latexmk`. [`DiCy`](https://yitzchak.github.io/dicy/) is included with this package so no further configuration or installation is required.",
127 | "type": "boolean",
128 | "default": "false",
129 | "order": 6
130 | },
131 | "enableExtendedBuildMode": {
132 | "description": "Enable extended build mode using `latexmk` rules for custom files types. Currently includes support for Asymptote, the `glossaries` package, the `index` package, MetaPost, the `nomencl` package and SageTeX. Please note that these rules are loaded after all other `latexmkrc` files are loaded, and therefore may overwrite custom rules defined by the user.",
133 | "type": "boolean",
134 | "default": true,
135 | "order": 7
136 | },
137 | "loggingLevel": {
138 | "description": "The minimum level of message severity to output in the logger. A logging level of `error` shows only messages indicating catastrophic issues such as undefined symbols, `warning` shows error messages and messages indicating unintended consequences such as bad boxes, and `info` shows all messages including purely informational messages such a font loading.",
139 | "type": "string",
140 | "enum": [
141 | "error",
142 | "warning",
143 | "info"
144 | ],
145 | "default": "warning",
146 | "order": 8
147 | },
148 | "cleanPatterns": {
149 | "description": "The files and directories to remove during a LaTeX clean. Basic glob patterns are understood and named properties such as {jobname} are replaced with the current build properties. Patterns that start with `/` or `\\` are matched against any file in the same directory as the source file. All other patterns are matched against generated files in the output directory. More information can be found on the Atom LaTeX wiki.",
150 | "type": "array",
151 | "items": {
152 | "type": "string"
153 | },
154 | "default": [
155 | "**/*.aux",
156 | "**/*.aux.bak",
157 | "**/*.bbl",
158 | "**/*.bcf",
159 | "**/*.blg",
160 | "**/*.dvi",
161 | "**/*.fdb_latexmk",
162 | "**/*.fls",
163 | "**/*.idx",
164 | "**/*.idx.bak",
165 | "**/*.ilg",
166 | "**/*.ind",
167 | "**/*.lof",
168 | "**/*.log",
169 | "**/*.lol",
170 | "**/*.lot",
171 | "**/*.nav",
172 | "**/*.out",
173 | "**/*.pdf",
174 | "**/*.ps",
175 | "**/*.snm",
176 | "**/*.synctex.gz",
177 | "**/*.toc",
178 | "/**/_minted-{jobname}",
179 | "/{output_dir}/sage-plots-for-{jobname}.tex",
180 | "/missfont.log",
181 | "/texput.log",
182 | "/texput.aux"
183 | ],
184 | "order": 9
185 | },
186 | "outputDirectory": {
187 | "description": "All files generated during a build will be redirected here. Leave blank if you want the build output to be stored in the same directory as the TeX document.",
188 | "type": "string",
189 | "default": "",
190 | "order": 10
191 | },
192 | "outputFormat": {
193 | "description": "Output file format. DVI and PS currently only supported for latexmk.",
194 | "type": "string",
195 | "enum": [
196 | "pdf",
197 | "dvi",
198 | "ps"
199 | ],
200 | "default": "pdf",
201 | "order": 11
202 | },
203 | "producer": {
204 | "title": "PDF Producer",
205 | "description": "Program to use when post-processing DVI output in order to produce PDF. This is only used for LaTeX engines not capable of natively producing PDF. Currently only supported for latexmk.",
206 | "type": "string",
207 | "enum": [
208 | "dvipdfmx",
209 | "xdvipdfmx",
210 | "dvipdf",
211 | "ps2pdf"
212 | ],
213 | "default": "dvipdfmx",
214 | "order": 12
215 | },
216 | "moveResultToSourceDirectory": {
217 | "title": "Move Result to Source Directory",
218 | "description": "Ensures that the output file produced by a successful build is stored together with the TeX document that produced it.",
219 | "type": "boolean",
220 | "default": true,
221 | "order": 13
222 | },
223 | "buildOnSave": {
224 | "title": "Build on Save",
225 | "description": "Automatically run builds when files are saved.",
226 | "type": "boolean",
227 | "default": false,
228 | "order": 14
229 | },
230 | "openResultAfterBuild": {
231 | "title": "Open Result after Successful Build",
232 | "type": "boolean",
233 | "default": true,
234 | "order": 15
235 | },
236 | "openResultInBackground": {
237 | "title": "Open Result in Background",
238 | "type": "boolean",
239 | "default": true,
240 | "order": 16
241 | },
242 | "opener": {
243 | "type": "string",
244 | "enum": [
245 | "automatic",
246 | "atril",
247 | "evince",
248 | "okular",
249 | "pdf-view",
250 | "preview",
251 | "qpdfview",
252 | "shell-open",
253 | "skim",
254 | "sumatra",
255 | "xdg-open",
256 | "x-reader",
257 | "zathura",
258 | "custom"
259 | ],
260 | "default": "automatic",
261 | "order": 17
262 | },
263 | "pdfViewSplitDirection": {
264 | "description": "Pane split direction to use for pdf-view.",
265 | "type": "string",
266 | "enum": [
267 | "left",
268 | "right",
269 | "up",
270 | "down"
271 | ],
272 | "default": "right",
273 | "order": 18
274 | },
275 | "skimPath": {
276 | "description": "Full application path to Skim (macOS).",
277 | "type": "string",
278 | "default": "/Applications/Skim.app",
279 | "order": 19
280 | },
281 | "sumatraPath": {
282 | "title": "SumatraPDF Path",
283 | "description": "Full application path to SumatraPDF (Windows).",
284 | "type": "string",
285 | "default": "C:\\Program Files (x86)\\SumatraPDF\\SumatraPDF.exe",
286 | "order": 20
287 | },
288 | "okularPath": {
289 | "description": "Full application path to Okular (*nix).",
290 | "type": "string",
291 | "default": "/usr/bin/okular",
292 | "order": 21
293 | },
294 | "zathuraPath": {
295 | "description": "Full application path to Zathura (*nix).",
296 | "type": "string",
297 | "default": "/usr/bin/zathura",
298 | "order": 22
299 | },
300 | "qpdfviewPath": {
301 | "description": "Full application path to qpdfview (*nix).",
302 | "type": "string",
303 | "default": "/usr/bin/qpdfview",
304 | "order": 23
305 | },
306 | "viewerPath": {
307 | "title": "Custom PDF Viewer Path",
308 | "description": "Full application path to your PDF viewer. Overrides Skim and SumatraPDF options.",
309 | "type": "string",
310 | "default": "",
311 | "order": 24
312 | }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/resources/latexmkrc:
--------------------------------------------------------------------------------
1 | #########################
2 | # Support for Asymptote #
3 | #########################
4 |
5 | add_cus_dep('asy', 'eps', 0, 'asymptote_eps');
6 | add_cus_dep('asy', 'pdf', 0, 'asymptote_pdf');
7 | add_cus_dep('asy', 'tex', 0, 'asymptote_tex');
8 |
9 | sub asymptote_eps {
10 | return asymptote($_[0], 'eps');
11 | }
12 |
13 | sub asymptote_pdf {
14 | return asymptote($_[0], 'pdf');
15 | }
16 |
17 | sub asymptote_tex {
18 | return asymptote($_[0], 'tex');
19 | }
20 |
21 | sub asymptote {
22 | $dir = dirname($_[0]);
23 | $file = basename($_[0]);
24 | my $ret = system("asy -offscreen -vv -f \"$_[1]\" -cd \"$dir\" \"$file\" >\"$_[0].asy_log\" 2>&1");
25 | my $log_handle = new FileHandle;
26 | open $log_handle, "$_[0].asy_log";
27 | %imp = ();
28 |
29 | while (<$log_handle>) {
30 | if (/^(Including|Loading) .* from (.*)\s*$/) {
31 | my $import = $2;
32 | $imp{$import} = 1;
33 | } elsif (/^(error|.*\.asy: \d)/) {
34 | warn "==Message from asy: $_";
35 | $ret = 1;
36 | } elsif (/^(kpsewhich|Processing|Using|Welcome|Wrote|cd|gs) /) {
37 | } else {
38 | warn "==Message from asy: $_";
39 | }
40 | }
41 | close $log_handle;
42 | if ($version_num >= '4.48') {
43 | rdb_set_source( $rule, keys %imp );
44 | }
45 | return $ret;
46 | }
47 |
48 | push @generated_exts, 'asy_log';
49 |
50 | ######################################
51 | # Support for the glossaries package #
52 | ######################################
53 |
54 | add_cus_dep('glo', 'gls', 0, 'makeglossaries');
55 | add_cus_dep('acn', 'acr', 0, 'makeglossaries');
56 |
57 | sub makeglossaries {
58 | $dir = dirname($_[0]);
59 | $file = basename($_[0]);
60 | system("makeglossaries -d \"$dir\" \"$file\"");
61 | }
62 |
63 | push @generated_exts, 'glo', 'gls', 'glg', 'acn', 'acr', 'alg';
64 |
65 | #################################
66 | # Support for the index package #
67 | #################################
68 |
69 | add_cus_dep('adx', 'and', 0, 'acro_index');
70 |
71 | sub acro_index {
72 | system( "makeindex -o \"$_[0].and\" \"$_[0].adx\"");
73 | }
74 |
75 | push @generated_exts, 'adx', 'and', 'alg';
76 |
77 | add_cus_dep('bdx', 'bnd', 0, 'bib_index');
78 |
79 | sub bib_index {
80 | system("makeindex -s bibref.ist -o \"$_[0].bnd\" \"$_[0].bdx\"");
81 | }
82 |
83 | push @generated_exts, 'bdx', 'bnd', 'blg';
84 |
85 | add_cus_dep('ndx', 'nnd', 0, 'name_index');
86 |
87 | sub name_index {
88 | system("makeindex -o \"$_[0].nnd\" \"$_[0].ndx\"");
89 | }
90 |
91 | push @generated_exts, 'ndx', 'nnd', 'nlg';
92 |
93 | add_cus_dep('ldx', 'lnd', 0, 'list_index');
94 |
95 | sub list_index {
96 | system("makeindex -o \"$_[0].lnd\" \"$_[0].ldx\"");
97 | }
98 |
99 | push @generated_exts, 'ldx', 'lnd', 'llg';
100 |
101 | add_cus_dep('tdx', 'tnd', 0, 'title_index');
102 |
103 | sub title_index {
104 | system("makeindex -o \"$_[0].tnd\" \"$_[0].tdx\"");
105 | }
106 |
107 | push @generated_exts, 'tdx', 'tnd', 'tlg';
108 |
109 | #####################################
110 | # Metapost support for feynmp, etc. #
111 | #####################################
112 |
113 | add_cus_dep('mp', '1', 0, 'mpost');
114 |
115 | sub mpost {
116 | $dir = dirname($_[0]);
117 | $file = basename($_[0]);
118 | return system("cd \"$dir\" && mpost \"$file\"");
119 | }
120 |
121 | push @generated_exts, '1';
122 |
123 | ########################################
124 | # Support for the nomenclature package #
125 | ########################################
126 |
127 | add_cus_dep("nlo", "nls", 0, "nomencl_index");
128 |
129 | sub nomencl_index {
130 | system("makeindex \"$_[0].nlo\" -s nomencl.ist -o \"$_[0].nls\" -t \"$_[0].nlg\"");
131 | }
132 |
133 | push @generated_exts, 'nlo', 'nls', 'nlg';
134 |
135 | #######################
136 | # Support for SageTeX #
137 | #######################
138 |
139 | add_cus_dep('sage', 'sout', 0, 'sage');
140 |
141 | $hash_calc_ignore_pattern{'sage'} = '^( _st_.goboom|print .SageT)';
142 |
143 | sub sage {
144 | $dir = dirname($_[0]);
145 | $file = basename($_[0]);
146 | system("cd \"$dir\" && sage \"$file.sage\"");
147 | }
148 |
149 | push @generated_exts, 'sage', 'sout', 'sage.py', 'scmd';
150 |
--------------------------------------------------------------------------------
/script/cibuild:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | # Enable case-insensitive pattern matching.
7 | shopt -s nocasematch
8 |
9 | ensure_xz_installed() {
10 | if [[ "${APPVEYOR:-}" == true ]]; then
11 | echo "Installing XZ..."
12 | local archive_path="/tmp/xz.7z"
13 | curl -Lso ${archive_path} "https://tukaani.org/xz/xz-5.2.3-windows.7z"
14 |
15 | local xz_root="${HOME}/xz"
16 | 7z x -o"${xz_root}" "${archive_path}" > /dev/null
17 |
18 | export PATH="${xz_root}/bin_x86-64:${PATH}"
19 | fi
20 | }
21 |
22 | initialize() {
23 | local script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P )"
24 | local scripts=(
25 | install-ghostscript
26 | install-knitr
27 | install-miktex
28 | install-texlive
29 | )
30 |
31 | for script in ${scripts[@]}; do
32 | source "${script_dir}/${script}"
33 | done
34 | }
35 |
36 | install_latex_distribution() {
37 | if [[ "${TEX_DIST:=texlive}" == "miktex" ]]; then
38 | install_miktex
39 | else
40 | install_texlive
41 | fi
42 | }
43 |
44 | exec_ci() {
45 | if [[ "${APPVEYOR:-}" == true ]]; then
46 | local script_path="/tmp/build-package.ps1"
47 | curl -so ${script_path} "https://raw.githubusercontent.com/atom/ci/master/build-package.ps1"
48 | powershell -noninteractive -noprofile -command "${script_path}"
49 | else
50 | curl -s "https://raw.githubusercontent.com/atom/ci/master/build-package.sh" | sh
51 | fi
52 | }
53 |
54 | main() {
55 | initialize
56 | ensure_xz_installed
57 | install_latex_distribution
58 | install_knitr
59 | install_ghostscript
60 | exec_ci
61 | }
62 |
63 | main "$@"
64 |
--------------------------------------------------------------------------------
/script/create-fixtures:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | shopt -s extglob
4 |
5 | cd spec/fixtures
6 |
7 | for i in pdf pdfps pdfdvi ps dvi; do
8 | latexmk -$i -outdir=log-parse -jobname=file-$i file.tex
9 | sed -i '/^ "\//d' log-parse/file-$i.fdb_latexmk
10 | done
11 |
12 | rm log-parse/*.!(log|fdb_latexmk)
13 |
--------------------------------------------------------------------------------
/script/install-ghostscript:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | # Enable case-insensitive pattern matching.
7 | shopt -s nocasematch
8 |
9 | install_ghostscript() {
10 | if [[ "${APPVEYOR:-}" == true ]]; then
11 | echo "Installing ghostscript..."
12 | choco install ghostscript
13 | elif [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then
14 | echo "Installing ghostscript..."
15 | brew update
16 | brew install ghostscript
17 | fi
18 | }
19 |
--------------------------------------------------------------------------------
/script/install-knitr:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | # Enable case-insensitive pattern matching.
7 | shopt -s nocasematch
8 |
9 | ensure_r_installed() {
10 | if [[ "${APPVEYOR:-}" == true ]]; then
11 | # TODO: Remove when 'R.Project' package adds it as a dependency.
12 | echo "Installing chocolatey-core.extension..."
13 | choco install chocolatey-core.extension
14 |
15 | echo "Installing R..."
16 | choco install R.Project --allow-empty-checksums --ia="/DIR=\"${R_HOME}\""
17 |
18 | export PATH="${R_HOME}/bin:${PATH}"
19 | elif [[ "${TRAVIS_OS_NAME:-}" == "osx" ]]; then
20 | echo "Installing R..."
21 | brew update
22 | brew install r
23 | fi
24 | }
25 |
26 | install_knitr() {
27 | ensure_r_installed
28 |
29 | # Check if we need to install Knitr.
30 | if ! Rscript -e "library(knitr); library(patchSynctex)" &> /dev/null; then
31 | # Make sure the R_LIBS_USER directory exists to make R happy.
32 | [[ ! -d "${R_LIBS_USER}" ]] && mkdir -p "${R_LIBS_USER}"
33 |
34 | echo "Installing knitr..."
35 | Rscript -e "install.packages(c('knitr', 'patchSynctex'), repos = 'http://cran.r-project.org')"
36 | else
37 | echo "Using cached installation of knitr"
38 | fi
39 | }
40 |
--------------------------------------------------------------------------------
/script/install-miktex:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | # Enable case-insensitive pattern matching.
7 | shopt -s nocasematch
8 |
9 | install_miktex() {
10 | echo "Installing MiKTeX..."
11 | curl -sL "https://www.dropbox.com/s/yop91s5bcq4moz5/miktex-portable.tar.xz?dl=1" | tar -xJC "${HOME}"
12 |
13 | # Ensure PATH points to the binaries.
14 | export PATH="${HOME}/miktex-portable/texmfs/install/miktex/bin:${HOME}/miktex-portable/asymptote:${PATH}"
15 | export ASYMPTOTE_HOME="${HOME}/miktex-portable/asymptote"
16 | }
17 |
--------------------------------------------------------------------------------
/script/install-texlive:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | # Enable case-insensitive pattern matching.
7 | shopt -s nocasematch
8 |
9 | get_download_url() {
10 | local platform="travis"
11 | [[ "${APPVEYOR:-}" == true ]] && platform="appveyor"
12 | echo "https://raw.githubusercontent.com/thomasjo/${platform}-texlive/master/texlive.tar.xz"
13 | }
14 |
15 | install_texlive() {
16 | echo "Downloading portable TeX Live installation..."
17 | curl -s $( get_download_url ) | tar -xJC "${HOME}"
18 |
19 | # Ensure PATH points to the platform-specific binaries.
20 | if [[ "${APPVEYOR:-}" == true ]]; then
21 | export PATH="${HOME}/texlive/bin/win32:${PATH}"
22 | elif [[ "${TRAVIS_OS_NAME:-}" == "linux" ]]; then
23 | export PATH="${HOME}/texlive/bin/x86_64-linux:${PATH}"
24 | else
25 | export PATH="${HOME}/texlive/bin/x86_64-darwin:${PATH}"
26 | fi
27 | }
28 |
--------------------------------------------------------------------------------
/script/lint:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Exit on failure, and treat expansion of unset variables as an error.
4 | set -eu
5 |
6 | lint() {
7 | local npm_bin=$( npm bin )
8 | local paths="$1"
9 |
10 | "${npm_bin}/standard" --verbose "${paths}" | "${npm_bin}/snazzy"
11 | }
12 |
13 | main() {
14 | local script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P )"
15 | local source_dir=$( dirname "${script_dir}" )
16 |
17 | local -i status=0
18 | echo "Linting package..." && lint "${source_dir}/lib/**/*.js" || status=$?
19 | echo "Linting package specs..." && lint "${source_dir}/spec/**/*.js" || status=$?
20 | return ${status}
21 | }
22 |
23 | main "$@"
24 |
--------------------------------------------------------------------------------
/spec/async-spec-helpers.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // This file has shamelessly been procured from
4 | // https://raw.githubusercontent.com/atom/atom/master/spec/async-spec-helpers.js
5 |
6 | export function beforeEach (fn) {
7 | global.beforeEach(function () {
8 | const result = fn()
9 | if (result instanceof Promise) {
10 | waitsForPromise(() => result)
11 | }
12 | })
13 | }
14 |
15 | export function afterEach (fn) {
16 | global.afterEach(function () {
17 | const result = fn()
18 | if (result instanceof Promise) {
19 | waitsForPromise(() => result)
20 | }
21 | })
22 | }
23 |
24 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
25 | module.exports[name] = function (description, fn) {
26 | if (fn === undefined) {
27 | global[name](description)
28 | return
29 | }
30 |
31 | global[name](description, function () {
32 | const result = fn()
33 | if (result instanceof Promise) {
34 | waitsForPromise(() => result)
35 | }
36 | })
37 | }
38 | })
39 |
40 | export async function conditionPromise (condition, description = 'anonymous condition') {
41 | const startTime = Date.now()
42 |
43 | while (true) {
44 | await timeoutPromise(100)
45 |
46 | if (await condition()) {
47 | return
48 | }
49 |
50 | if (Date.now() - startTime > 5000) {
51 | throw new Error('Timed out waiting on ' + description)
52 | }
53 | }
54 | }
55 |
56 | export function timeoutPromise (timeout) {
57 | return new Promise(function (resolve) {
58 | global.setTimeout(resolve, timeout)
59 | })
60 | }
61 |
62 | function waitsForPromise (fn) {
63 | const promise = fn()
64 | global.waitsFor('spec promise to resolve', function (done) {
65 | promise.then(done, function (error) {
66 | jasmine.getEnv().currentSpec.fail(error)
67 | done()
68 | })
69 | })
70 | }
71 |
72 | export function emitterEventPromise (emitter, event, timeout = 15000) {
73 | return new Promise((resolve, reject) => {
74 | const timeoutHandle = setTimeout(() => {
75 | reject(new Error(`Timed out waiting for '${event}' event`))
76 | }, timeout)
77 | emitter.once(event, () => {
78 | clearTimeout(timeoutHandle)
79 | resolve()
80 | })
81 | })
82 | }
83 |
84 | export function promisify (original) {
85 | return function (...args) {
86 | return new Promise((resolve, reject) => {
87 | args.push((err, ...results) => {
88 | if (err) {
89 | reject(err)
90 | } else {
91 | resolve(...results)
92 | }
93 | })
94 |
95 | return original(...args)
96 | })
97 | }
98 | }
99 |
100 | export function promisifySome (obj, fnNames) {
101 | const result = {}
102 | for (const fnName of fnNames) {
103 | result[fnName] = promisify(obj[fnName])
104 | }
105 | return result
106 | }
107 |
--------------------------------------------------------------------------------
/spec/build-registry-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 | import { activatePackages } from './spec-helpers'
6 | import { NullBuilder } from './stubs'
7 |
8 | import BuilderRegistry from '../lib/builder-registry'
9 | import BuildState from '../lib/build-state'
10 |
11 | describe('BuilderRegistry', () => {
12 | let builderRegistry
13 |
14 | beforeEach(async () => {
15 | await activatePackages()
16 |
17 | atom.config.set('latex.builder', 'latexmk')
18 | builderRegistry = new BuilderRegistry()
19 | })
20 |
21 | describe('getBuilderImplementation', () => {
22 | it('returns null when no builders are associated with the given file', () => {
23 | const state = new BuildState('quux.txt')
24 | expect(builderRegistry.getBuilderImplementation(state)).toBeNull()
25 | })
26 |
27 | it('returns the configured builder when given a regular .tex file', () => {
28 | const state = new BuildState('foo.tex')
29 | expect(builderRegistry.getBuilderImplementation(state).name).toEqual('LatexmkBuilder')
30 | })
31 |
32 | it('throws an error when unable to resolve ambiguous builder registration', () => {
33 | const allBuilders = builderRegistry.getAllBuilders().push(NullBuilder)
34 | const state = new BuildState('foo.tex')
35 | spyOn(builderRegistry, 'getAllBuilders').andReturn(allBuilders)
36 | expect(() => { builderRegistry.getBuilderImplementation(state) }).toThrow()
37 | })
38 |
39 | it('returns the Knitr builder when presented with an .Rnw file', () => {
40 | const state = new BuildState('bar.Rnw')
41 | expect(builderRegistry.getBuilderImplementation(state).name).toEqual('KnitrBuilder')
42 | })
43 | })
44 |
45 | describe('getBuilder', () => {
46 | beforeEach(() => {
47 | atom.config.set('latex.builder', 'latexmk')
48 | })
49 |
50 | it('returns null when passed an unhandled file type', () => {
51 | const state = new BuildState('quux.txt')
52 | expect(builderRegistry.getBuilder(state)).toBeNull()
53 | })
54 |
55 | it('returns a builder instance as configured for regular .tex files', () => {
56 | const state = new BuildState('foo.tex')
57 | expect(builderRegistry.getBuilder(state).constructor.name).toEqual('LatexmkBuilder')
58 | })
59 |
60 | it('returns a builder instance as configured for knitr files', () => {
61 | const state = new BuildState('bar.Rnw')
62 | expect(builderRegistry.getBuilder(state).constructor.name).toEqual('KnitrBuilder')
63 | })
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/spec/builder-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 | import { cloneFixtures } from './spec-helpers'
6 |
7 | import path from 'path'
8 | import Builder from '../lib/builder'
9 | import BuildState from '../lib/build-state'
10 |
11 | describe('Builder', () => {
12 | let builder, fixturesPath, filePath, logFilePath, fdbFilePath, state, jobState
13 |
14 | beforeEach(() => {
15 | builder = new Builder()
16 | fixturesPath = cloneFixtures()
17 | filePath = path.join(fixturesPath, 'file.tex')
18 | logFilePath = path.join(fixturesPath, 'file.log')
19 | fdbFilePath = path.join(fixturesPath, 'file.fdb_latexmk')
20 | state = new BuildState(filePath)
21 | state.setOutputDirectory('')
22 | jobState = state.getJobStates()[0]
23 | })
24 |
25 | describe('constructPath', () => {
26 | it('reads `latex.texPath` as configured', () => {
27 | spyOn(atom.config, 'get').andReturn()
28 | builder.constructPath()
29 |
30 | expect(atom.config.get).toHaveBeenCalledWith('latex.texPath')
31 | })
32 |
33 | it('uses platform default when `latex.texPath` is not configured', () => {
34 | const defaultTexPath = '/foo/bar'
35 | const expectedPath = [defaultTexPath, process.env.PATH].join(path.delimiter)
36 | atom.config.set('latex.texPath', '')
37 | spyOn(builder, 'defaultTexPath').andReturn(defaultTexPath)
38 |
39 | const constructedPath = builder.constructPath()
40 | expect(constructedPath).toBe(expectedPath)
41 | })
42 |
43 | it('replaces surrounded $PATH with process.env.PATH', () => {
44 | const texPath = '/foo:$PATH:/bar'
45 | const expectedPath = texPath.replace('$PATH', process.env.PATH)
46 | atom.config.set('latex.texPath', texPath)
47 |
48 | const constructedPath = builder.constructPath()
49 | expect(constructedPath).toBe(expectedPath)
50 | })
51 |
52 | it('replaces leading $PATH with process.env.PATH', () => {
53 | const texPath = '$PATH:/bar'
54 | const expectedPath = texPath.replace('$PATH', process.env.PATH)
55 | atom.config.set('latex.texPath', texPath)
56 |
57 | const constructedPath = builder.constructPath()
58 | expect(constructedPath).toBe(expectedPath)
59 | })
60 |
61 | it('replaces trailing $PATH with process.env.PATH', () => {
62 | const texPath = '/foo:$PATH'
63 | const expectedPath = texPath.replace('$PATH', process.env.PATH)
64 | atom.config.set('latex.texPath', texPath)
65 |
66 | const constructedPath = builder.constructPath()
67 | expect(constructedPath).toBe(expectedPath)
68 | })
69 |
70 | it('prepends process.env.PATH with texPath', () => {
71 | const texPath = '/foo'
72 | const expectedPath = [texPath, process.env.PATH].join(path.delimiter)
73 | atom.config.set('latex.texPath', texPath)
74 |
75 | const constructedPath = builder.constructPath()
76 | expect(constructedPath).toBe(expectedPath)
77 | })
78 | })
79 |
80 | describe('parseLogFile', () => {
81 | let logParser
82 |
83 | beforeEach(() => {
84 | logParser = jasmine.createSpyObj('MockLogParser', ['parse'])
85 | spyOn(builder, 'getLogParser').andReturn(logParser)
86 | })
87 |
88 | it('resolves the associated log file path by invoking @resolveLogFilePath', () => {
89 | spyOn(builder, 'resolveLogFilePath').andReturn('foo.log')
90 |
91 | builder.parseLogFile(jobState)
92 | expect(builder.resolveLogFilePath).toHaveBeenCalledWith(jobState)
93 | })
94 |
95 | it('does not attempt parse if passed a file path that does not exist', () => {
96 | state.setFilePath('/foo/bar/quux.tex')
97 | builder.parseLogFile(jobState)
98 |
99 | expect(logParser.parse).not.toHaveBeenCalled()
100 | })
101 |
102 | it('attempts to parse the resolved log file', () => {
103 | builder.parseLogFile(jobState)
104 |
105 | expect(builder.getLogParser).toHaveBeenCalledWith(logFilePath, filePath)
106 | expect(logParser.parse).toHaveBeenCalled()
107 | })
108 | })
109 |
110 | describe('parseFdbFile', () => {
111 | let fdbParser
112 |
113 | beforeEach(() => {
114 | fdbParser = jasmine.createSpyObj('MockFdbParser', ['parse'])
115 | spyOn(builder, 'getFdbParser').andReturn(fdbParser)
116 | })
117 |
118 | it('resolves the associated fdb file path by invoking @resolveFdbFilePath', () => {
119 | spyOn(builder, 'resolveFdbFilePath').andReturn('foo.fdb_latexmk')
120 |
121 | builder.parseFdbFile(jobState)
122 | expect(builder.resolveFdbFilePath).toHaveBeenCalledWith(jobState)
123 | })
124 |
125 | it('does not attempt parse if passed a file path that does not exist', () => {
126 | state.setFilePath('/foo/bar/quux.tex')
127 | builder.parseFdbFile(jobState)
128 |
129 | expect(fdbParser.parse).not.toHaveBeenCalled()
130 | })
131 |
132 | it('attempts to parse the resolved fdb file', () => {
133 | builder.parseFdbFile(jobState)
134 |
135 | expect(builder.getFdbParser).toHaveBeenCalledWith(fdbFilePath)
136 | expect(fdbParser.parse).toHaveBeenCalled()
137 | })
138 | })
139 |
140 | describe('parseLogAndFdbFiles', () => {
141 | it('verifies that the correct output file is selected when using various latexmk modes', () => {
142 | const switches = [
143 | { name: 'pdf', format: 'pdf' },
144 | { name: 'pdfdvi', format: 'pdf' },
145 | { name: 'pdfps', format: 'pdf' },
146 | { name: 'ps', format: 'ps' },
147 | { name: 'dvi', format: 'dvi' }]
148 | state.setOutputDirectory('log-parse')
149 |
150 | for (const { name, format } of switches) {
151 | state.setJobNames([`file-${name}`])
152 | jobState = state.getJobStates()[0]
153 | builder.parseLogAndFdbFiles(jobState)
154 | expect(path.basename(jobState.getOutputFilePath())).toBe(`${jobState.getJobName()}.${format}`,
155 | `Select ${format} file when using -${name} switch.`)
156 | }
157 | })
158 | })
159 | })
160 |
--------------------------------------------------------------------------------
/spec/builders/knitr-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from '../async-spec-helpers'
5 | import { activatePackages, cloneFixtures } from '../spec-helpers'
6 |
7 | import fs from 'fs-plus'
8 | import path from 'path'
9 | import KnitrBuilder from '../../lib/builders/knitr'
10 | import BuildState from '../../lib/build-state'
11 |
12 | function getRawFile (filePath) {
13 | return fs.readFileSync(filePath, { encoding: 'utf-8' })
14 | }
15 |
16 | describe('KnitrBuilder', () => {
17 | let builder, fixturesPath, filePath, state, jobState
18 |
19 | beforeEach(async () => {
20 | await activatePackages()
21 |
22 | builder = new KnitrBuilder()
23 | spyOn(builder, 'logStatusCode').andCallThrough()
24 |
25 | fixturesPath = cloneFixtures()
26 | filePath = path.join(fixturesPath, 'knitr', 'file.Rnw')
27 |
28 | state = new BuildState(filePath)
29 | state.setEngine('pdflatex')
30 | state.setOutputFormat('pdf')
31 | state.setOutputDirectory('')
32 | jobState = state.getJobStates()[0]
33 | })
34 |
35 | describe('constructArgs', () => {
36 | it('produces default arguments containing expected file path', () => {
37 | const expectedArgs = [
38 | '-e "library(knitr)"',
39 | '-e "opts_knit$set(concordance = TRUE)"',
40 | `-e "knit('${filePath.replace(/\\/g, '\\\\')}')"`
41 | ]
42 |
43 | const args = builder.constructArgs(jobState)
44 | expect(args).toEqual(expectedArgs)
45 | })
46 | })
47 |
48 | describe('constructPatchSynctexArgs', () => {
49 | it('produces default arguments containing expected file path', () => {
50 | const escapedFilePath = filePath.replace(/\\/g, '\\\\')
51 | const escapedSynctexPath = escapedFilePath.replace(/\.[^.]+$/, '')
52 | const expectedArgs = [
53 | '-e "library(patchSynctex)"',
54 | `-e "patchSynctex('${escapedFilePath}',syncfile='${escapedSynctexPath}')"`
55 | ]
56 |
57 | const args = builder.constructPatchSynctexArgs(jobState)
58 | expect(args).toEqual(expectedArgs)
59 | })
60 | })
61 |
62 | describe('run', () => {
63 | it('successfully executes knitr when given a valid R Sweave file', async () => {
64 | const outputFilePath = path.join(fixturesPath, 'knitr', 'file.tex')
65 |
66 | const exitCode = await builder.run(jobState)
67 |
68 | expect(exitCode).toBe(0)
69 | expect(builder.logStatusCode).not.toHaveBeenCalled()
70 | expect(getRawFile(outputFilePath)).toContain('$\\tau \\approx 6.2831853$')
71 | })
72 |
73 | it('fails to execute knitr when given an invalid file path', async () => {
74 | filePath = path.join(fixturesPath, 'foo.Rnw')
75 | state.setFilePath(filePath)
76 |
77 | const exitCode = await builder.run(jobState)
78 |
79 | runs(() => {
80 | expect(exitCode).toBe(1)
81 | expect(builder.logStatusCode).toHaveBeenCalled()
82 | })
83 | })
84 |
85 | it('detects missing knitr library and logs an error', async () => {
86 | const directoryPath = path.dirname(filePath)
87 | const env = { 'R_LIBS_USER': '/dev/null', 'R_LIBS_SITE': '/dev/null' }
88 | const options = builder.constructChildProcessOptions(directoryPath)
89 | Object.assign(options.env, env)
90 | spyOn(builder, 'constructChildProcessOptions').andReturn(options)
91 | spyOn(latex.log, 'showMessages').andCallThrough()
92 |
93 | const exitCode = await builder.run(jobState)
94 |
95 | expect(exitCode).toBe(-1)
96 | expect(builder.logStatusCode).toHaveBeenCalled()
97 | expect(latex.log.showMessages).toHaveBeenCalledWith([{
98 | type: 'error',
99 | text: 'The R package "knitr" could not be loaded.'
100 | }])
101 | })
102 | })
103 |
104 | describe('resolveOutputPath', () => {
105 | let sourcePath, resultPath
106 |
107 | beforeEach(() => {
108 | sourcePath = path.resolve('/var/foo.Rnw')
109 | resultPath = path.resolve('/var/foo.tex')
110 | })
111 |
112 | it('detects an absolute path and returns it unchanged', () => {
113 | const stdout = `foo\nbar\n\n[1] "${resultPath}"`
114 | const resolvedPath = builder.resolveOutputPath(sourcePath, stdout)
115 |
116 | expect(resolvedPath).toBe(resultPath)
117 | })
118 |
119 | it('detects a relative path and makes it absolute with respect to the source file', () => {
120 | const stdout = `foo\nbar\n\n[1] "${path.basename(resultPath)}"`
121 | const resolvedPath = builder.resolveOutputPath(sourcePath, stdout)
122 |
123 | expect(resolvedPath).toBe(resultPath)
124 | })
125 | })
126 |
127 | describe('canProcess', () => {
128 | it('returns true when given a file path with a .Rnw extension', () => {
129 | const canProcess = KnitrBuilder.canProcess(state)
130 | expect(canProcess).toBe(true)
131 | })
132 | })
133 | })
134 |
--------------------------------------------------------------------------------
/spec/fixtures/_minted-wibble/default.pygstyle:
--------------------------------------------------------------------------------
1 |
2 | \makeatletter
3 | \def\PYGdefault@reset{\let\PYGdefault@it=\relax \let\PYGdefault@bf=\relax%
4 | \let\PYGdefault@ul=\relax \let\PYGdefault@tc=\relax%
5 | \let\PYGdefault@bc=\relax \let\PYGdefault@ff=\relax}
6 | \def\PYGdefault@tok#1{\csname PYGdefault@tok@#1\endcsname}
7 | \def\PYGdefault@toks#1+{\ifx\relax#1\empty\else%
8 | \PYGdefault@tok{#1}\expandafter\PYGdefault@toks\fi}
9 | \def\PYGdefault@do#1{\PYGdefault@bc{\PYGdefault@tc{\PYGdefault@ul{%
10 | \PYGdefault@it{\PYGdefault@bf{\PYGdefault@ff{#1}}}}}}}
11 | \def\PYGdefault#1#2{\PYGdefault@reset\PYGdefault@toks#1+\relax+\PYGdefault@do{#2}}
12 |
13 | \expandafter\def\csname PYGdefault@tok@sb\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
14 | \expandafter\def\csname PYGdefault@tok@mh\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
15 | \expandafter\def\csname PYGdefault@tok@sh\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
16 | \expandafter\def\csname PYGdefault@tok@sd\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
17 | \expandafter\def\csname PYGdefault@tok@gu\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}}
18 | \expandafter\def\csname PYGdefault@tok@o\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
19 | \expandafter\def\csname PYGdefault@tok@nt\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
20 | \expandafter\def\csname PYGdefault@tok@bp\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
21 | \expandafter\def\csname PYGdefault@tok@nd\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}}
22 | \expandafter\def\csname PYGdefault@tok@ow\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}}
23 | \expandafter\def\csname PYGdefault@tok@ni\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.60,0.60,0.60}{##1}}}
24 | \expandafter\def\csname PYGdefault@tok@c\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
25 | \expandafter\def\csname PYGdefault@tok@sr\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}}
26 | \expandafter\def\csname PYGdefault@tok@kc\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
27 | \expandafter\def\csname PYGdefault@tok@gp\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}}
28 | \expandafter\def\csname PYGdefault@tok@gh\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}}
29 | \expandafter\def\csname PYGdefault@tok@cpf\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
30 | \expandafter\def\csname PYGdefault@tok@ss\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}}
31 | \expandafter\def\csname PYGdefault@tok@cp\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.74,0.48,0.00}{##1}}}
32 | \expandafter\def\csname PYGdefault@tok@c1\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
33 | \expandafter\def\csname PYGdefault@tok@kt\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.69,0.00,0.25}{##1}}}
34 | \expandafter\def\csname PYGdefault@tok@sc\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
35 | \expandafter\def\csname PYGdefault@tok@mb\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
36 | \expandafter\def\csname PYGdefault@tok@m\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
37 | \expandafter\def\csname PYGdefault@tok@nl\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.63,0.63,0.00}{##1}}}
38 | \expandafter\def\csname PYGdefault@tok@mf\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
39 | \expandafter\def\csname PYGdefault@tok@s2\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
40 | \expandafter\def\csname PYGdefault@tok@ch\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
41 | \expandafter\def\csname PYGdefault@tok@ge\endcsname{\let\PYGdefault@it=\textit}
42 | \expandafter\def\csname PYGdefault@tok@cs\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
43 | \expandafter\def\csname PYGdefault@tok@vg\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}}
44 | \expandafter\def\csname PYGdefault@tok@vc\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}}
45 | \expandafter\def\csname PYGdefault@tok@kn\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
46 | \expandafter\def\csname PYGdefault@tok@nc\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}}
47 | \expandafter\def\csname PYGdefault@tok@no\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.53,0.00,0.00}{##1}}}
48 | \expandafter\def\csname PYGdefault@tok@k\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
49 | \expandafter\def\csname PYGdefault@tok@nb\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
50 | \expandafter\def\csname PYGdefault@tok@vi\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}}
51 | \expandafter\def\csname PYGdefault@tok@gt\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}}
52 | \expandafter\def\csname PYGdefault@tok@gr\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}}
53 | \expandafter\def\csname PYGdefault@tok@nv\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}}
54 | \expandafter\def\csname PYGdefault@tok@go\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.53,0.53,0.53}{##1}}}
55 | \expandafter\def\csname PYGdefault@tok@kd\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
56 | \expandafter\def\csname PYGdefault@tok@nn\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}}
57 | \expandafter\def\csname PYGdefault@tok@kp\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
58 | \expandafter\def\csname PYGdefault@tok@se\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.40,0.13}{##1}}}
59 | \expandafter\def\csname PYGdefault@tok@cm\endcsname{\let\PYGdefault@it=\textit\def\PYGdefault@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}}
60 | \expandafter\def\csname PYGdefault@tok@gi\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}}
61 | \expandafter\def\csname PYGdefault@tok@err\endcsname{\def\PYGdefault@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}}
62 | \expandafter\def\csname PYGdefault@tok@il\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
63 | \expandafter\def\csname PYGdefault@tok@s\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
64 | \expandafter\def\csname PYGdefault@tok@si\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}}
65 | \expandafter\def\csname PYGdefault@tok@gd\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}}
66 | \expandafter\def\csname PYGdefault@tok@s1\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}}
67 | \expandafter\def\csname PYGdefault@tok@sx\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
68 | \expandafter\def\csname PYGdefault@tok@na\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.49,0.56,0.16}{##1}}}
69 | \expandafter\def\csname PYGdefault@tok@kr\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}}
70 | \expandafter\def\csname PYGdefault@tok@w\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}}
71 | \expandafter\def\csname PYGdefault@tok@nf\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}}
72 | \expandafter\def\csname PYGdefault@tok@gs\endcsname{\let\PYGdefault@bf=\textbf}
73 | \expandafter\def\csname PYGdefault@tok@ne\endcsname{\let\PYGdefault@bf=\textbf\def\PYGdefault@tc##1{\textcolor[rgb]{0.82,0.25,0.23}{##1}}}
74 | \expandafter\def\csname PYGdefault@tok@mi\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
75 | \expandafter\def\csname PYGdefault@tok@mo\endcsname{\def\PYGdefault@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}}
76 |
77 | \def\PYGdefaultZbs{\char`\\}
78 | \def\PYGdefaultZus{\char`\_}
79 | \def\PYGdefaultZob{\char`\{}
80 | \def\PYGdefaultZcb{\char`\}}
81 | \def\PYGdefaultZca{\char`\^}
82 | \def\PYGdefaultZam{\char`\&}
83 | \def\PYGdefaultZlt{\char`\<}
84 | \def\PYGdefaultZgt{\char`\>}
85 | \def\PYGdefaultZsh{\char`\#}
86 | \def\PYGdefaultZpc{\char`\%}
87 | \def\PYGdefaultZdl{\char`\$}
88 | \def\PYGdefaultZhy{\char`\-}
89 | \def\PYGdefaultZsq{\char`\'}
90 | \def\PYGdefaultZdq{\char`\"}
91 | \def\PYGdefaultZti{\char`\~}
92 | % for compatibility with earlier versions
93 | \def\PYGdefaultZat{@}
94 | \def\PYGdefaultZlb{[}
95 | \def\PYGdefaultZrb{]}
96 | \makeatother
97 |
98 |
--------------------------------------------------------------------------------
/spec/fixtures/error-warning.tex:
--------------------------------------------------------------------------------
1 | % !TEX jobnames = foo bar, snafu
2 | \documentclass{article}
3 |
4 | \begin{document}
5 |
6 | \begin{center}
7 | % Error: There's no line here to end
8 | \hbox{Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
9 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
10 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
11 | id est laborum} \\
12 | \end{center}
13 |
14 | % Error: Argument of \@sect has an extra }.
15 | % Error: Paragraph ended before \@sect was complete.
16 | \section{Foo\footnote{Bar}}
17 |
18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
19 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
20 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
21 | id est laborum
22 |
23 | \begin{tabular}{ll}
24 | % Extra alignment tab has bee changed to \cr
25 | Foo & Bar & Snafu \\
26 | \end{tabular}
27 |
28 | % Generate errors and warnings in a subfile with spaces
29 | \input{"sub/foo bar.tex"}
30 |
31 | % Generate errors and warnings in a subfile located in another directory
32 | \input{sub/wibble}
33 |
34 | \end{document}
35 |
--------------------------------------------------------------------------------
/spec/fixtures/errors.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014) (preloaded format=pdflatex 2014.9.23) 11 OCT 2014 10:14
2 | entering extended mode
3 | \write18 enabled.
4 | file:line:error style messages enabled.
5 | %&-line parsing enabled.
6 | **errors.tex
7 | (./errors.tex
8 | LaTeX2e <2014/05/01>
9 | Babel <3.9k> and hyphenation patterns for 79 languages loaded.
10 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/article.cls
11 | Document Class: article 2007/10/19 v1.4h Standard LaTeX document class
12 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/size10.clo
13 | File: size10.clo 2007/10/19 v1.4h Standard LaTeX file (size option)
14 | )
15 | \c@part=\count79
16 | \c@section=\count80
17 | \c@subsection=\count81
18 | \c@subsubsection=\count82
19 | \c@paragraph=\count83
20 | \c@subparagraph=\count84
21 | \c@figure=\count85
22 | \c@table=\count86
23 | \abovecaptionskip=\skip41
24 | \belowcaptionskip=\skip42
25 | \bibindent=\dimen102
26 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/mathtools/mathtools.sty
27 | Package: mathtools 2014/07/16 v1.15 mathematical typesetting tools
28 | (/usr/local/texlive/2014/texmf-dist/tex/latex/graphics/keyval.sty
29 | Package: keyval 2014/05/08 v1.15 key=value parser (DPC)
30 | \KV@toks@=\toks14
31 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/tools/calc.sty
32 | Package: calc 2007/08/22 v4.3 Infix arithmetic (KKT,FJ)
33 | \calc@Acount=\count87
34 | \calc@Bcount=\count88
35 | \calc@Adimen=\dimen103
36 | \calc@Bdimen=\dimen104
37 | \calc@Askip=\skip43
38 | \calc@Bskip=\skip44
39 | LaTeX Info: Redefining \setlength on input line 75.
40 | LaTeX Info: Redefining \addtolength on input line 76.
41 | \calc@Ccount=\count89
42 | \calc@Cskip=\skip45
43 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/mathtools/mhsetup.sty
44 | Package: mhsetup 2010/01/21 v1.2a programming setup (MH)
45 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/amsmath/amsmath.sty
46 | Package: amsmath 2013/01/14 v2.14 AMS math features
47 | \@mathmargin=\skip46
48 |
49 | For additional information on amsmath, use the `?' option.
50 | (/usr/local/texlive/2014/texmf-dist/tex/latex/amsmath/amstext.sty
51 | Package: amstext 2000/06/29 v2.01
52 | (/usr/local/texlive/2014/texmf-dist/tex/latex/amsmath/amsgen.sty
53 | File: amsgen.sty 1999/11/30 v2.0
54 | \@emptytoks=\toks15
55 | \ex@=\dimen105
56 | )) (/usr/local/texlive/2014/texmf-dist/tex/latex/amsmath/amsbsy.sty
57 | Package: amsbsy 1999/11/29 v1.2d
58 | \pmbraise@=\dimen106
59 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/amsmath/amsopn.sty
60 | Package: amsopn 1999/12/14 v2.01 operator names
61 | )
62 | \inf@bad=\count90
63 | LaTeX Info: Redefining \frac on input line 210.
64 | \uproot@=\count91
65 | \leftroot@=\count92
66 | LaTeX Info: Redefining \overline on input line 306.
67 | \classnum@=\count93
68 | \DOTSCASE@=\count94
69 | LaTeX Info: Redefining \ldots on input line 378.
70 | LaTeX Info: Redefining \dots on input line 381.
71 | LaTeX Info: Redefining \cdots on input line 466.
72 | \Mathstrutbox@=\box26
73 | \strutbox@=\box27
74 | \big@size=\dimen107
75 | LaTeX Font Info: Redeclaring font encoding OML on input line 566.
76 | LaTeX Font Info: Redeclaring font encoding OMS on input line 567.
77 | \macc@depth=\count95
78 | \c@MaxMatrixCols=\count96
79 | \dotsspace@=\muskip10
80 | \c@parentequation=\count97
81 | \dspbrk@lvl=\count98
82 | \tag@help=\toks16
83 | \row@=\count99
84 | \column@=\count100
85 | \maxfields@=\count101
86 | \andhelp@=\toks17
87 | \eqnshift@=\dimen108
88 | \alignsep@=\dimen109
89 | \tagshift@=\dimen110
90 | \tagwidth@=\dimen111
91 | \totwidth@=\dimen112
92 | \lineht@=\dimen113
93 | \@envbody=\toks18
94 | \multlinegap=\skip47
95 | \multlinetaggap=\skip48
96 | \mathdisplay@stack=\toks19
97 | LaTeX Info: Redefining \[ on input line 2665.
98 | LaTeX Info: Redefining \] on input line 2666.
99 | )
100 | LaTeX Info: Thecontrolsequence`\['isalreadyrobust on input line 129.
101 | LaTeX Info: Thecontrolsequence`\]'isalreadyrobust on input line 129.
102 | \g_MT_multlinerow_int=\count102
103 | \l_MT_multwidth_dim=\dimen114
104 | \origjot=\skip49
105 | \l_MT_shortvdotswithinadjustabove_dim=\dimen115
106 | \l_MT_shortvdotswithinadjustbelow_dim=\dimen116
107 | \l_MT_above_intertext_sep=\dimen117
108 | \l_MT_below_intertext_sep=\dimen118
109 | \l_MT_above_shortintertext_sep=\dimen119
110 | \l_MT_below_shortintertext_sep=\dimen120
111 | ) (/foo/output/errors.aux)
112 | \openout1 = `errors.aux'.
113 |
114 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5.
115 | LaTeX Font Info: ... okay on input line 5.
116 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5.
117 | LaTeX Font Info: ... okay on input line 5.
118 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5.
119 | LaTeX Font Info: ... okay on input line 5.
120 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5.
121 | LaTeX Font Info: ... okay on input line 5.
122 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5.
123 | LaTeX Font Info: ... okay on input line 5.
124 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5.
125 | LaTeX Font Info: ... okay on input line 5.
126 | (/usr/local/texlive/2014/texmf-dist/tex/latex/graphics/graphicx.sty
127 | Package: graphicx 2014/04/25 v1.0g Enhanced LaTeX Graphics (DPC,SPQR)
128 | (/usr/local/texlive/2014/texmf-dist/tex/latex/graphics/graphics.sty
129 | Package: graphics 2009/02/05 v1.0o Standard LaTeX Graphics (DPC,SPQR)
130 | (/usr/local/texlive/2014/texmf-dist/tex/latex/graphics/trig.sty
131 | Package: trig 1999/03/16 v1.09 sin cos tan (DPC)
132 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/latexconfig/graphics.cfg
133 | File: graphics.cfg 2010/04/23 v1.9 graphics configuration of TeX Live
134 | )
135 | Package graphics Info: Driver file: pdftex.def on input line 91.
136 | (/usr/local/texlive/2014/texmf-dist/tex/latex/pdftex-def/pdftex.def
137 | File: pdftex.def 2011/05/27 v0.06d Graphics/color for pdfTeX
138 | (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/infwarerr.sty
139 | Package: infwarerr 2010/04/08 v1.3 Providing info/warning/error messages (HO)
140 | ) (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ltxcmds.sty
141 | Package: ltxcmds 2011/11/09 v1.22 LaTeX kernel commands for general use (HO)
142 | )
143 | \Gread@gobject=\count103
144 | (/usr/local/texlive/2014/texmf-dist/tex/context/base/supp-pdf.mkii
145 | [Loading MPS to PDF converter (version 2006.09.02).]
146 | \scratchcounter=\count104
147 | \scratchdimen=\dimen121
148 | \scratchbox=\box28
149 | \nofMPsegments=\count105
150 | \nofMParguments=\count106
151 | \everyMPshowfont=\toks20
152 | \MPscratchCnt=\count107
153 | \MPscratchDim=\dimen122
154 | \MPnumerator=\count108
155 | \makeMPintoPDFobject=\count109
156 | \everyMPtoPDFconversion=\toks21
157 | ))) (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty
158 | Package: pdftexcmds 2011/11/29 v0.20 Utility functions of pdfTeX for LuaTeX (HO)
159 | (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ifluatex.sty
160 | Package: ifluatex 2010/03/01 v1.3 Provides the ifluatex switch (HO)
161 | Package ifluatex Info: LuaTeX not detected.
162 | ) (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ifpdf.sty
163 | Package: ifpdf 2011/01/30 v2.3 Provides the ifpdf switch (HO)
164 | Package ifpdf Info: pdfTeX in PDF mode is detected.
165 | )
166 | Package pdftexcmds Info: LuaTeX not detected.
167 | Package pdftexcmds Info: \pdf@primitive is available.
168 | Package pdftexcmds Info: \pdf@ifprimitive is available.
169 | Package pdftexcmds Info: \pdfdraftmode found.
170 | ) (/usr/local/texlive/2014/texmf-dist/tex/latex/oberdiek/epstopdf-base.sty
171 | Package: epstopdf-base 2010/02/09 v2.5 Base part for package epstopdf
172 | (/usr/local/texlive/2014/texmf-dist/tex/latex/oberdiek/grfext.sty
173 | Package: grfext 2010/08/19 v1.1 Manage graphics extensions (HO)
174 | (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/kvdefinekeys.sty
175 | Package: kvdefinekeys 2011/04/07 v1.3 Define keys (HO)
176 | )) (/usr/local/texlive/2014/texmf-dist/tex/latex/oberdiek/kvoptions.sty
177 | Package: kvoptions 2011/06/30 v3.11 Key value format for package options (HO)
178 | (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty
179 | Package: kvsetkeys 2012/04/25 v1.16 Key value parser (HO)
180 | (/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/etexcmds.sty
181 | Package: etexcmds 2011/02/16 v1.5 Avoid name clashes with e-TeX commands (HO)
182 | Package etexcmds Info: Could not find \expanded.
183 | (etexcmds) That can mean that you are not using pdfTeX 1.50 or
184 | (etexcmds) that some package has redefined \expanded.
185 | (etexcmds) In the latter case, load this package earlier.
186 | )))
187 | Package grfext Info: Graphics extension search list:
188 | (grfext) [.png,.pdf,.jpg,.mps,.jpeg,.jbig2,.jb2,.PNG,.PDF,.JPG,.JPEG,.JBIG2,.JB2,.eps]
189 | (grfext) \AppendGraphicsExtensions on input line 452.
190 | (/usr/local/texlive/2014/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
191 | File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Live
192 | ))
193 | \Gin@req@height=\dimen123
194 | \Gin@req@width=\dimen124
195 | )
196 |
197 | ./errors.tex:10: LaTeX Error: \begin{gather*} on input line 8 ended by \end{gather}.
198 |
199 | See the LaTeX manual or LaTeX Companion for explanation.
200 | Type H for immediate help.
201 | ...
202 |
203 | l.10 \end{gather}
204 |
205 | Your command was ignored.
206 | Type I to replace it with another command,
207 | or to continue without it.
208 |
209 |
210 | ./errors.tex:12: LaTeX Error: Environment foo undefined.
211 |
212 | See the LaTeX manual or LaTeX Companion for explanation.
213 | Type H for immediate help.
214 | ...
215 |
216 | l.12 \begin{foo}
217 | \end{foo}
218 | Your command was ignored.
219 | Type I to replace it with another command,
220 | or to continue without it.
221 |
222 |
223 | ./errors.tex:12: LaTeX Error: \begin{document} ended by \end{foo}.
224 |
225 | See the LaTeX manual or LaTeX Companion for explanation.
226 | Type H for immediate help.
227 | ...
228 |
229 | l.12 \begin{foo}\end{foo}
230 |
231 | Your command was ignored.
232 | Type I to replace it with another command,
233 | or to continue without it.
234 |
235 | [1
236 |
237 | {/usr/local/texlive/2014/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/foo/output/errors.aux) )
238 | Here is how much of TeX's memory you used:
239 | 2617 strings out of 493108
240 | 37850 string characters out of 6134847
241 | 100329 words of memory out of 5000000
242 | 6070 multiletter control sequences out of 15000+600000
243 | 5846 words of font info for 22 fonts, out of 8000000 for 9000
244 | 1141 hyphenation exceptions out of 8191
245 | 51i,7n,27p,253b,119s stack positions out of 5000i,500n,10000p,200000b,80000s
246 |
247 | Output written on /foo/output/errors.pdf (1 page, 28092 bytes).
248 | PDF statistics:
249 | 20 PDF objects out of 1000 (max. 8388607)
250 | 13 compressed objects within 1 object stream
251 | 0 named destinations out of 1000 (max. 500000)
252 | 1 words of extra memory for PDF output out of 10000 (max. 10000000)
253 |
--------------------------------------------------------------------------------
/spec/fixtures/file.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["pdflatex"] 1474559881 "file.tex" "/foo/output/file.pdf" "file" 1474559881
3 | "/foo/output/file.aux" 1474559881 78 176b9809a80d53064f21009e02135ec4 ""
4 | "/usr/local/texlive/2016/texmf-dist/fonts/map/fontname/texfonts.map" 1272929888 3287 e6b82fe08f5336d4d5ebc73fb1152e87 ""
5 | "/usr/local/texlive/2016/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm" 1136768653 1328 c834bbb027764024c09d3d2bf908b5f0 ""
6 | "/usr/local/texlive/2016/texmf-dist/fonts/tfm/public/cm/cmbx12.tfm" 1136768653 1324 c910af8c371558dc20f2d7822f66fe64 ""
7 | "/usr/local/texlive/2016/texmf-dist/fonts/tfm/public/cm/cmmi12.tfm" 1136768653 1524 4414a8315f39513458b80dfc63bff03a ""
8 | "/usr/local/texlive/2016/texmf-dist/fonts/tfm/public/cm/cmr12.tfm" 1136768653 1288 655e228510b4c2a1abe905c368440826 ""
9 | "/usr/local/texlive/2016/texmf-dist/fonts/tfm/public/cm/cmsy10.tfm" 1136768653 1124 6c73e740cf17375f03eec0ee63599741 ""
10 | "/usr/local/texlive/2016/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb" 1248133631 34811 78b52f49e893bcba91bd7581cdc144c0 ""
11 | "/usr/local/texlive/2016/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx12.pfb" 1248133631 32080 340ef9bf63678554ee606688e7b5339d ""
12 | "/usr/local/texlive/2016/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb" 1248133631 35752 024fb6c41858982481f6968b5fc26508 ""
13 | "/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls" 1459635588 19821 310da678527a7dfe2a02c88af38079b7 ""
14 | "/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo" 1459635588 8292 e897c12e1e886ce77fe26afc5d470886 ""
15 | "/usr/local/texlive/2016/texmf-dist/web2c/texmf.cnf" 1470266149 31958 926ad82bc47ee89401cf24be7d2cbf7d ""
16 | "/usr/local/texlive/2016/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1471876454 1938064 4ded6634052b89be330bb7dac8cc40f0 ""
17 | "/usr/local/texlive/2016/texmf-var/web2c/pdftex/pdflatex.fmt" 1471547796 3873677 271c6d05ce0a4f4e34be832490419ac2 ""
18 | "/usr/local/texlive/2016/texmf.cnf" 1465311657 1020 2966fa5b7c5b86b293259f0ebdcea22d ""
19 | "file.tex" 1474559763 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
20 | "output/file.aux" 1474559881 78 176b9809a80d53064f21009e02135ec4 ""
21 | (generated)
22 | "/foo/output/file.pdfsync"
23 | "/foo/output/file.pdf"
24 | "output/file.log"
25 | "output/file.pdf"
26 | "/foo/output/file.log"
27 | "output/file.aux"
28 |
--------------------------------------------------------------------------------
/spec/fixtures/file.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014) (preloaded format=pdflatex 2014.6.22) 6 JUL 2014 22:06
2 | entering extended mode
3 | \write18 enabled.
4 | file:line:error style messages enabled.
5 | %&-line parsing enabled.
6 | **file.tex
7 | (./file.tex
8 | LaTeX2e <2014/05/01>
9 | Babel <3.9k> and hyphenation patterns for 78 languages loaded.
10 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/article.cls
11 | Document Class: article 2007/10/19 v1.4h Standard LaTeX document class
12 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/size10.clo
13 | File: size10.clo 2007/10/19 v1.4h Standard LaTeX file (size option)
14 | )
15 | \c@part=\count79
16 | \c@section=\count80
17 | \c@subsection=\count81
18 | \c@subsubsection=\count82
19 | \c@paragraph=\count83
20 | \c@subparagraph=\count84
21 | \c@figure=\count85
22 | \c@table=\count86
23 | \abovecaptionskip=\skip41
24 | \belowcaptionskip=\skip42
25 | \bibindent=\dimen102
26 | ) (/foo/output/file.aux)
27 | \openout1 = `file.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | {/usr/local/texlive/2014/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/foo/output/file.aux) )
48 | Here is how much of TeX's memory you used:
49 | 213 strings out of 493117
50 | 2674 string characters out of 6135433
51 | 52909 words of memory out of 5000000
52 | 3758 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on /foo/output/file.pdf (1 page, 28094 bytes).
58 | PDF statistics:
59 | 20 PDF objects out of 1000 (max. 8388607)
60 | 13 compressed objects within 1 object stream
61 | 0 named destinations out of 1000 (max. 500000)
62 | 1 words of extra memory for PDF output out of 10000 (max. 10000000)
63 |
--------------------------------------------------------------------------------
/spec/fixtures/file.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \begin{document}
4 | \section{\LaTeX}
5 | Lorem ipsum dolor sit amet...
6 | \end{document}
7 |
--------------------------------------------------------------------------------
/spec/fixtures/filename with spaces.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014) (preloaded format=pdflatex 2014.6.22) 6 JUL 2014 22:06
2 | entering extended mode
3 | \write18 enabled.
4 | file:line:error style messages enabled.
5 | %&-line parsing enabled.
6 | **file.tex
7 | (./file.tex
8 | LaTeX2e <2014/05/01>
9 | Babel <3.9k> and hyphenation patterns for 78 languages loaded.
10 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/article.cls
11 | Document Class: article 2007/10/19 v1.4h Standard LaTeX document class
12 | (/usr/local/texlive/2014/texmf-dist/tex/latex/base/size10.clo
13 | File: size10.clo 2007/10/19 v1.4h Standard LaTeX file (size option)
14 | )
15 | \c@part=\count79
16 | \c@section=\count80
17 | \c@subsection=\count81
18 | \c@subsubsection=\count82
19 | \c@paragraph=\count83
20 | \c@subparagraph=\count84
21 | \c@figure=\count85
22 | \c@table=\count86
23 | \abovecaptionskip=\skip41
24 | \belowcaptionskip=\skip42
25 | \bibindent=\dimen102
26 | ) (/foo/output/file.aux)
27 | \openout1 = `file.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | {/usr/local/texlive/2014/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/foo/output/filename with spaces.aux) )
48 | Here is how much of TeX's memory you used:
49 | 213 strings out of 493117
50 | 2674 string characters out of 6135433
51 | 52909 words of memory out of 5000000
52 | 3758 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on "/foo/output/filename with spaces.pdf" (1 page, 28094 bytes).
58 | PDF statistics:
59 | 20 PDF objects out of 1000 (max. 8388607)
60 | 13 compressed objects within 1 object stream
61 | 0 named destinations out of 1000 (max. 500000)
62 | 1 words of extra memory for PDF output out of 10000 (max. 10000000)
63 |
--------------------------------------------------------------------------------
/spec/fixtures/filename with spaces.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \begin{document}
4 | \section{\LaTeX}
5 | Lorem ipsum dolor sit amet...
6 | \end{document}
7 |
--------------------------------------------------------------------------------
/spec/fixtures/knitr/file.Rnw:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \begin{document}
4 | \section{\LaTeX + knitr}
5 | <>=
6 | tau = 2 * pi
7 | @
8 |
9 | $\tau \approx \Sexpr{round(tau, 10)}$
10 | \end{document}
11 |
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/asymptote-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \usepackage[inline]{asymptote}
4 |
5 | \begin{document}
6 |
7 | \begin{center}
8 | \begin{asy}
9 | import graph;
10 | size(300,IgnoreAspect);
11 |
12 | bool3 branch(real x)
13 | {
14 | static int lastsign=0;
15 | if(x <= 0 && x == floor(x)) return false;
16 | int sign=sgn(gamma(x));
17 | bool b=lastsign == 0 || sign == lastsign;
18 | lastsign=sign;
19 | return b ? true : default;
20 | }
21 |
22 | draw(graph(gamma,-4,4,n=2000,branch),red);
23 |
24 | scale(false);
25 | xlimits(-4,4);
26 | ylimits(-6,6);
27 | crop();
28 |
29 | xaxis("$x$",RightTicks(NoZero));
30 | yaxis(LeftTicks(NoZero));
31 |
32 | label("$\Gamma(x)$",(1,2),red);
33 | \end{asy}
34 | \end{center}
35 |
36 | \end{document}
37 |
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/glossaries-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \usepackage[acronym]{glossaries}
4 |
5 | \makeglossaries
6 |
7 | \newglossaryentry{foo}{
8 | name=foo,
9 | description={Metasyntactic variable used as a placeholder often in conjunction
10 | with bar}
11 | }
12 |
13 | \newglossaryentry{bar}{
14 | name=bar,
15 | description={Metasyntactic variable used as a placeholder often in conjunction
16 | with foo}
17 | }
18 |
19 | \newacronym{gce}{GCE}{Gross Conceptual Error}
20 | \newacronym{rawq}{RAWQ}{Right Answer; Wrong Question}
21 |
22 | \begin{document}
23 |
24 | Without \gls{foo} there is no \gls{bar} which often leads to a \acrlong{gce},
25 | which is abbreviated \acrshort{gce}. This is often preceded by \gls{rawq}.
26 |
27 | \printglossary[type=\acronymtype]
28 | \printglossary
29 |
30 | \end{document}
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/index-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 | \usepackage{index}
3 |
4 | \makeindex
5 | \newindex[theenumi]{list}{ldx}{lnd}{Items}
6 |
7 | \begin{document}
8 |
9 | Wibble, wibble, foo\index{foo}, foo, bar\index{bar}.
10 |
11 | \begin{enumerate}
12 | \item Gronk
13 | \item Baz\index[list]{baz}
14 | \item Quux\index[list]{quux}
15 | \end{enumerate}
16 |
17 | \printindex[list]
18 | \printindex
19 |
20 | \end{document}
21 |
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/mpost-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \usepackage[pdftex]{graphicx}
4 | \usepackage{feynmp}
5 |
6 | \DeclareGraphicsRule{.1}{mps}{*}{}
7 |
8 | \begin{document}
9 |
10 | \begin{fmffile}{mpost-test-feynmp}
11 | \begin{fmfgraph*}(120,80)
12 | \fmfpen{thin}
13 | \fmfleft{i1,i2}
14 | \fmfright{o1,o2}
15 | \fmf{fermion}{i1,v1,o1}
16 | \fmf{fermion}{i2,v2,o2}
17 | \fmf{photon,label=$\gamma$}{v1,v2}
18 | \fmfdot{v1,v2}
19 | \end{fmfgraph*}
20 | \end{fmffile}
21 |
22 | \end{document}
23 |
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/nomencl-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \usepackage{nomencl}
4 | \makenomenclature
5 |
6 | \begin{document}
7 |
8 | \section{Wibble}
9 |
10 | \nomenclature{$G$}{Universal gravitational constant}
11 | \nomenclature{$\hbar$}{Dirac constant (reduced Planck constant)}
12 |
13 | \printnomenclature
14 | \end{document}
--------------------------------------------------------------------------------
/spec/fixtures/latexmk/sagetex-test.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 | \usepackage{sagetex}
3 |
4 | \begin{document}
5 |
6 | \begin{sageblock}
7 | f(x) = x^2 * exp(x)
8 | \end{sageblock}
9 |
10 | The second derivative of $f$ is
11 |
12 | \[
13 | \frac{\mathrm{d}^{2}}{\mathrm{d}x^{2}} \left[ \sage{f(x)} \right] =
14 | \sage{diff(f, x, 2)(x)}.
15 | \]
16 |
17 | \end{document}
18 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-dvi.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["latex"] 1479339799 "file.tex" "log-parse/file-dvi.dvi" "file-dvi" 1479339799
3 | "file-dvi.aux" 0 -1 0 ""
4 | "file.tex" 1476733827 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
5 | (generated)
6 | "log-parse/file-dvi.log"
7 | "log-parse/file-dvi.dvi"
8 | "log-parse/file-dvi.aux"
9 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-dvi.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=latex 2016.11.6) 16 NOV 2016 18:43
2 | entering extended mode
3 | \write18 enabled.
4 | %&-line parsing enabled.
5 | **file.tex
6 | (./file.tex
7 | LaTeX2e <2016/03/31> patch level 3
8 | Babel <3.9r> and hyphenation patterns for 83 language(s) loaded.
9 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls
10 | Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
11 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo
12 | File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
13 | )
14 | \c@part=\count79
15 | \c@section=\count80
16 | \c@subsection=\count81
17 | \c@subsubsection=\count82
18 | \c@paragraph=\count83
19 | \c@subparagraph=\count84
20 | \c@figure=\count85
21 | \c@table=\count86
22 | \abovecaptionskip=\skip41
23 | \belowcaptionskip=\skip42
24 | \bibindent=\dimen102
25 | )
26 | No file file-dvi.aux.
27 | \openout1 = `file-dvi.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | ] (log-parse/file-dvi.aux) )
48 | Here is how much of TeX's memory you used:
49 | 211 strings out of 493014
50 | 2323 string characters out of 6133369
51 | 53629 words of memory out of 5000000
52 | 3839 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on log-parse/file-dvi.dvi (1 page, 388 bytes).
58 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdf.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["pdflatex"] 1479339798 "file.tex" "log-parse/file-pdf.pdf" "file-pdf" 1479339798
3 | "file-pdf.aux" 0 -1 0 ""
4 | "file.tex" 1476733827 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
5 | (generated)
6 | "log-parse/file-pdf.pdf"
7 | "log-parse/file-pdf.log"
8 | "log-parse/file-pdf.aux"
9 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdf.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=pdflatex 2016.11.6) 16 NOV 2016 18:43
2 | entering extended mode
3 | \write18 enabled.
4 | %&-line parsing enabled.
5 | **file.tex
6 | (./file.tex
7 | LaTeX2e <2016/03/31> patch level 3
8 | Babel <3.9r> and hyphenation patterns for 83 language(s) loaded.
9 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls
10 | Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
11 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo
12 | File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
13 | )
14 | \c@part=\count79
15 | \c@section=\count80
16 | \c@subsection=\count81
17 | \c@subsubsection=\count82
18 | \c@paragraph=\count83
19 | \c@subparagraph=\count84
20 | \c@figure=\count85
21 | \c@table=\count86
22 | \abovecaptionskip=\skip41
23 | \belowcaptionskip=\skip42
24 | \bibindent=\dimen102
25 | )
26 | No file file-pdf.aux.
27 | \openout1 = `file-pdf.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | {/usr/local/texlive/2016/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
48 | (log-parse/file-pdf.aux) )
49 | Here is how much of TeX's memory you used:
50 | 212 strings out of 493013
51 | 2333 string characters out of 6133343
52 | 53632 words of memory out of 5000000
53 | 3839 multiletter control sequences out of 15000+600000
54 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
55 | 1141 hyphenation exceptions out of 8191
56 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
57 |
61 | Output written on log-parse/file-pdf.pdf (1 page, 28091 bytes).
62 | PDF statistics:
63 | 20 PDF objects out of 1000 (max. 8388607)
64 | 13 compressed objects within 1 object stream
65 | 0 named destinations out of 1000 (max. 500000)
66 | 1 words of extra memory for PDF output out of 10000 (max. 10000000)
67 |
68 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdfdvi.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["dvipdf"] 1479339798 "log-parse/file-pdfdvi.dvi" "log-parse/file-pdfdvi.pdf" "log-parse/file-pdfdvi" 1479339799
3 | "log-parse/file-pdfdvi.dvi" 1479339798 388 f41d63987574afa39860a8b64bdc7c18 "latex"
4 | (generated)
5 | "log-parse/file-pdfdvi.pdf"
6 | ["latex"] 1479339798 "file.tex" "log-parse/file-pdfdvi.dvi" "file-pdfdvi" 1479339799
7 | "file-pdfdvi.aux" 0 -1 0 ""
8 | "file.tex" 1476733827 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
9 | (generated)
10 | "log-parse/file-pdfdvi.dvi"
11 | "log-parse/file-pdfdvi.log"
12 | "log-parse/file-pdfdvi.aux"
13 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdfdvi.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=latex 2016.11.6) 16 NOV 2016 18:43
2 | entering extended mode
3 | \write18 enabled.
4 | %&-line parsing enabled.
5 | **file.tex
6 | (./file.tex
7 | LaTeX2e <2016/03/31> patch level 3
8 | Babel <3.9r> and hyphenation patterns for 83 language(s) loaded.
9 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls
10 | Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
11 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo
12 | File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
13 | )
14 | \c@part=\count79
15 | \c@section=\count80
16 | \c@subsection=\count81
17 | \c@subsubsection=\count82
18 | \c@paragraph=\count83
19 | \c@subparagraph=\count84
20 | \c@figure=\count85
21 | \c@table=\count86
22 | \abovecaptionskip=\skip41
23 | \belowcaptionskip=\skip42
24 | \bibindent=\dimen102
25 | )
26 | No file file-pdfdvi.aux.
27 | \openout1 = `file-pdfdvi.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | ] (log-parse/file-pdfdvi.aux) )
48 | Here is how much of TeX's memory you used:
49 | 211 strings out of 493014
50 | 2338 string characters out of 6133369
51 | 53629 words of memory out of 5000000
52 | 3839 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on log-parse/file-pdfdvi.dvi (1 page, 388 bytes).
58 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdfps.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["dvips"] 1479339798 "log-parse/file-pdfps.dvi" "log-parse/file-pdfps.ps" "log-parse/file-pdfps" 1479339798
3 | "log-parse/file-pdfps.dvi" 1479339798 388 f41d63987574afa39860a8b64bdc7c18 "latex"
4 | (generated)
5 | "log-parse/file-pdfps.ps"
6 | ["latex"] 1479339798 "file.tex" "log-parse/file-pdfps.dvi" "file-pdfps" 1479339798
7 | "file-pdfps.aux" 0 -1 0 ""
8 | "file.tex" 1476733827 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
9 | (generated)
10 | "log-parse/file-pdfps.aux"
11 | "log-parse/file-pdfps.log"
12 | "log-parse/file-pdfps.dvi"
13 | ["ps2pdf"] 1479339798 "log-parse/file-pdfps.ps" "log-parse/file-pdfps.pdf" "log-parse/file-pdfps" 1479339798
14 | "log-parse/file-pdfps.ps" 1479339798 58953 def11a7aa0ae54e8f8c1eff9332043a0 "dvips"
15 | (generated)
16 | "log-parse/file-pdfps.pdf"
17 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-pdfps.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=latex 2016.11.6) 16 NOV 2016 18:43
2 | entering extended mode
3 | \write18 enabled.
4 | %&-line parsing enabled.
5 | **file.tex
6 | (./file.tex
7 | LaTeX2e <2016/03/31> patch level 3
8 | Babel <3.9r> and hyphenation patterns for 83 language(s) loaded.
9 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls
10 | Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
11 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo
12 | File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
13 | )
14 | \c@part=\count79
15 | \c@section=\count80
16 | \c@subsection=\count81
17 | \c@subsubsection=\count82
18 | \c@paragraph=\count83
19 | \c@subparagraph=\count84
20 | \c@figure=\count85
21 | \c@table=\count86
22 | \abovecaptionskip=\skip41
23 | \belowcaptionskip=\skip42
24 | \bibindent=\dimen102
25 | )
26 | No file file-pdfps.aux.
27 | \openout1 = `file-pdfps.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | ] (log-parse/file-pdfps.aux) )
48 | Here is how much of TeX's memory you used:
49 | 211 strings out of 493014
50 | 2333 string characters out of 6133369
51 | 53629 words of memory out of 5000000
52 | 3839 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on log-parse/file-pdfps.dvi (1 page, 388 bytes).
58 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-ps.fdb_latexmk:
--------------------------------------------------------------------------------
1 | # Fdb version 3
2 | ["dvips"] 1479339799 "log-parse/file-ps.dvi" "log-parse/file-ps.ps" "log-parse/file-ps" 1479339799
3 | "log-parse/file-ps.dvi" 1479339799 388 f41d63987574afa39860a8b64bdc7c18 "latex"
4 | (generated)
5 | "log-parse/file-ps.ps"
6 | ["latex"] 1479339799 "file.tex" "log-parse/file-ps.dvi" "file-ps" 1479339799
7 | "file-ps.aux" 0 -1 0 ""
8 | "file.tex" 1476733827 108 e2f7eacca0e6c4d22260c2d4b1150d46 ""
9 | (generated)
10 | "log-parse/file-ps.aux"
11 | "log-parse/file-ps.log"
12 | "log-parse/file-ps.dvi"
13 |
--------------------------------------------------------------------------------
/spec/fixtures/log-parse/file-ps.log:
--------------------------------------------------------------------------------
1 | This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=latex 2016.11.6) 16 NOV 2016 18:43
2 | entering extended mode
3 | \write18 enabled.
4 | %&-line parsing enabled.
5 | **file.tex
6 | (./file.tex
7 | LaTeX2e <2016/03/31> patch level 3
8 | Babel <3.9r> and hyphenation patterns for 83 language(s) loaded.
9 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/article.cls
10 | Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
11 | (/usr/local/texlive/2016/texmf-dist/tex/latex/base/size10.clo
12 | File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
13 | )
14 | \c@part=\count79
15 | \c@section=\count80
16 | \c@subsection=\count81
17 | \c@subsubsection=\count82
18 | \c@paragraph=\count83
19 | \c@subparagraph=\count84
20 | \c@figure=\count85
21 | \c@table=\count86
22 | \abovecaptionskip=\skip41
23 | \belowcaptionskip=\skip42
24 | \bibindent=\dimen102
25 | )
26 | No file file-ps.aux.
27 | \openout1 = `file-ps.aux'.
28 |
29 | LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
30 | LaTeX Font Info: ... okay on input line 3.
31 | LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
32 | LaTeX Font Info: ... okay on input line 3.
33 | LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
34 | LaTeX Font Info: ... okay on input line 3.
35 | LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
36 | LaTeX Font Info: ... okay on input line 3.
37 | LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
38 | LaTeX Font Info: ... okay on input line 3.
39 | LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
40 | LaTeX Font Info: ... okay on input line 3.
41 | LaTeX Font Info: External font `cmex10' loaded for size
42 | (Font) <14.4> on input line 4.
43 | LaTeX Font Info: External font `cmex10' loaded for size
44 | (Font) <7> on input line 4.
45 | [1
46 |
47 | ] (log-parse/file-ps.aux) )
48 | Here is how much of TeX's memory you used:
49 | 211 strings out of 493014
50 | 2318 string characters out of 6133369
51 | 53629 words of memory out of 5000000
52 | 3839 multiletter control sequences out of 15000+600000
53 | 5168 words of font info for 19 fonts, out of 8000000 for 9000
54 | 1141 hyphenation exceptions out of 8191
55 | 23i,4n,20p,112b,117s stack positions out of 5000i,500n,10000p,200000b,80000s
56 |
57 | Output written on log-parse/file-ps.dvi (1 page, 388 bytes).
58 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/multiple-magic-comments.tex:
--------------------------------------------------------------------------------
1 | % !TEX root = ../file.tex
2 | % !TEX program = lualatex
3 | \documentclass{article}
4 |
5 | \begin{document}
6 | \section{\LaTeX}
7 | Lorem ipsum dolor sit amet...
8 | \end{document}
9 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/no-whitespace.tex:
--------------------------------------------------------------------------------
1 | %!TEX root=../file.tex
2 | \documentclass{article}
3 |
4 | \begin{document}
5 | \section{\LaTeX}
6 | Lorem ipsum dolor sit amet...
7 | \end{document}
8 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/not-first-line.tex:
--------------------------------------------------------------------------------
1 | % foo
2 | % !TEX root = ../file.tex
3 | \documentclass{article}
4 |
5 | \begin{document}
6 | \section{\LaTeX}
7 | Lorem ipsum dolor sit amet...
8 | \end{document}
9 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/override-settings.tex:
--------------------------------------------------------------------------------
1 | % !TEX program = lualatex
2 | % !TEX format = ps
3 | % !TEX jobnames = foo bar, snafu
4 | % !TEX producer = xdvipdfmx
5 | % !TEX output_directory = wibble
6 | % !TEX cleanPatterns = **/*.quux, foo/bar
7 | % !TEX enableShellEscape = yes
8 | % !TEX enableSynctex = true
9 | % !TEX enableExtendedBuildMode = YES
10 | % !TEX moveResultToSourceDirectory = Yes
11 | \documentclass{article}
12 |
13 | \begin{document}
14 | \section{\LaTeX}
15 | Lorem ipsum dolor sit amet...
16 | \end{document}
17 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/override-settings.yaml:
--------------------------------------------------------------------------------
1 | engine: xelatex
2 | format: dvi
3 | jobNames:
4 | - wibble
5 | - quux
6 | producer: ps2pdf
7 | outputDirectory: foo
8 | cleanPatterns:
9 | - '**/*.snafu'
10 | - foo/bar/bax
11 | enableShellEscape: yes
12 | enableSynctex: true
13 | enableExtendedBuildMode: YES
14 | moveResultToSourceDirectory: Yes
15 |
--------------------------------------------------------------------------------
/spec/fixtures/magic-comments/root-comment.tex:
--------------------------------------------------------------------------------
1 | % !TEX root = ../file.tex
2 | \documentclass{article}
3 |
4 | \begin{document}
5 | \section{\LaTeX}
6 | Lorem ipsum dolor sit amet...
7 | \end{document}
8 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/multiple-masters/inc1.tex:
--------------------------------------------------------------------------------
1 | \section{inc1}
2 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/multiple-masters/inc2.tex:
--------------------------------------------------------------------------------
1 | \section{inc2}
2 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/multiple-masters/master1.tex:
--------------------------------------------------------------------------------
1 | \documentclass[a4paper]{article}
2 |
3 |
4 | \begin{document}
5 |
6 | \include{inc1.tex}
7 | \end{document}
8 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/multiple-masters/master2.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 |
4 | \begin{document}
5 |
6 | \include{inc2.tex}
7 | \end{document}
8 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/single-master/inc1.tex:
--------------------------------------------------------------------------------
1 | % !TEX root = master.tex
2 |
3 | \section{Inc1}
4 | Inc1
5 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/single-master/inc2.tex:
--------------------------------------------------------------------------------
1 | % inc2
2 |
3 | \section{Inc2}
4 | Inc2
5 |
6 | \include{inc3}
7 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/single-master/inc3.tex:
--------------------------------------------------------------------------------
1 | % inc3
2 |
3 | \section{Inc3}
4 | Inc3
5 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/single-master/master.log:
--------------------------------------------------------------------------------
1 | Master log
2 |
--------------------------------------------------------------------------------
/spec/fixtures/master-tex-finder/single-master/master.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \begin{document}
4 | \include{inc1.tex}
5 | \include{inc2.tex}
6 | \input{inc3.tex}
7 | \end{document}
8 |
--------------------------------------------------------------------------------
/spec/fixtures/sub/foo bar.tex:
--------------------------------------------------------------------------------
1 | % Class errors, warnings, etc.
2 | \ClassError{foo}{Significant class issue}{RTFM!}
3 | \ClassWarning{foo}{Class issue}
4 | \ClassWarningNoLine{foo}{Nebulous class issue}
5 | \ClassInfo{foo}{Insignificant class issue}
6 |
--------------------------------------------------------------------------------
/spec/fixtures/sub/wibble.tex:
--------------------------------------------------------------------------------
1 | % Package errors, warnings, etc.
2 | \PackageError{bar}{Significant package issue}{RTFM!}
3 | \PackageWarning{bar}{Package issue}
4 | \PackageWarningNoLine{bar}{Nebulous package issue}
5 | \PackageInfo{bar}{Insignificant package issue}
6 |
7 | % Undefined references
8 | \ref{tab:snafu}
9 |
--------------------------------------------------------------------------------
/spec/latex-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 | import { activatePackages } from './spec-helpers'
6 |
7 | describe('Latex', () => {
8 | beforeEach(async () => {
9 | await activatePackages()
10 | })
11 |
12 | describe('initialize', () => {
13 | it('initializes all properties', () => {
14 | expect(latex.log).toBeDefined()
15 | expect(latex.opener).toBeDefined()
16 | expect(latex.process).toBeDefined()
17 | })
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/spec/logger-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 |
6 | import Logger from '../lib/logger'
7 | import werkzeug from '../lib/werkzeug'
8 |
9 | describe('Logger', () => {
10 | let logger, messagesListener
11 |
12 | function initialize (loggingLevel = 'warning') {
13 | logger = new Logger()
14 | messagesListener = jasmine.createSpy('onMessagesListener')
15 | logger.onMessages(messagesListener)
16 |
17 | atom.config.set('latex.loggingLevel', loggingLevel)
18 |
19 | logger.info()
20 | logger.warning()
21 | logger.error()
22 | }
23 |
24 | describe('getMessages', () => {
25 | it('verifies no messages filtered when logging level set to info', () => {
26 | initialize('info')
27 |
28 | expect(logger.getMessages()).toEqual([
29 | { type: 'info' },
30 | { type: 'warning' },
31 | { type: 'error' }
32 | ])
33 | })
34 |
35 | it('verifies info messages filtered when logging level set to warning', () => {
36 | initialize('warning')
37 |
38 | expect(logger.getMessages()).toEqual([
39 | { type: 'warning' },
40 | { type: 'error' }
41 | ])
42 | })
43 |
44 | it('verifies warning and info messages filtered when logging level set to error', () => {
45 | initialize('error')
46 |
47 | expect(logger.getMessages()).toEqual([
48 | { type: 'error' }
49 | ])
50 | })
51 |
52 | it('verifies no messages filtered when useFilters is false', () => {
53 | initialize('error')
54 |
55 | expect(logger.getMessages(false)).toEqual([
56 | { type: 'info' },
57 | { type: 'warning' },
58 | { type: 'error' }
59 | ])
60 | })
61 | })
62 |
63 | describe('messageTypeIsVisible', () => {
64 | it('verifies messageTypeIsVisible is true for all levels when logging level set to info', () => {
65 | initialize('info')
66 |
67 | expect(logger.messageTypeIsVisible('info')).toBe(true)
68 | expect(logger.messageTypeIsVisible('warning')).toBe(true)
69 | expect(logger.messageTypeIsVisible('error')).toBe(true)
70 | })
71 |
72 | it('verifies messageTypeIsVisible is false for info when logging level set to warning', () => {
73 | initialize('warning')
74 |
75 | expect(logger.messageTypeIsVisible('info')).toBe(false)
76 | expect(logger.messageTypeIsVisible('warning')).toBe(true)
77 | expect(logger.messageTypeIsVisible('error')).toBe(true)
78 | })
79 |
80 | it('verifies messageTypeIsVisible is false for info when logging level set to warning', () => {
81 | initialize('error')
82 |
83 | expect(logger.messageTypeIsVisible('info')).toBe(false)
84 | expect(logger.messageTypeIsVisible('warning')).toBe(false)
85 | expect(logger.messageTypeIsVisible('error')).toBe(true)
86 | })
87 | })
88 |
89 | describe('sync', () => {
90 | let logDock
91 |
92 | function initializeSpies (filePath, position) {
93 | initialize()
94 |
95 | logDock = jasmine.createSpyObj('LogDock', ['update'])
96 | spyOn(logger, 'show').andCallFake(() => Promise.resolve(logDock))
97 | spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath, position })
98 | }
99 |
100 | it('silently does nothing when the current editor is transient', async () => {
101 | initializeSpies(null, null)
102 |
103 | await logger.sync()
104 |
105 | expect(logger.show).not.toHaveBeenCalled()
106 | expect(logDock.update).not.toHaveBeenCalled()
107 | })
108 |
109 | it('shows and updates the log panel with the file path and position', async () => {
110 | const filePath = 'file.tex'
111 | const position = [[0, 0], [0, 10]]
112 |
113 | initializeSpies(filePath, position)
114 |
115 | await logger.sync()
116 |
117 | expect(logger.show).toHaveBeenCalled()
118 | expect(logDock.update).toHaveBeenCalledWith({ filePath, position })
119 | })
120 | })
121 |
122 | describe('onMessages', () => {
123 | it('verifies no messages filtered when logging level set to info', () => {
124 | initialize('info')
125 |
126 | expect(messagesListener).toHaveBeenCalledWith({
127 | messages: [{ type: 'info' }],
128 | reset: false
129 | })
130 | expect(messagesListener).toHaveBeenCalledWith({
131 | messages: [{ type: 'warning' }],
132 | reset: false
133 | })
134 | expect(messagesListener).toHaveBeenCalledWith({
135 | messages: [{ type: 'error' }],
136 | reset: false
137 | })
138 | })
139 |
140 | it('verifies info messages filtered when logging level set to warning', () => {
141 | initialize('warning')
142 |
143 | expect(messagesListener).not.toHaveBeenCalledWith({
144 | messages: [{ type: 'info' }],
145 | reset: false
146 | })
147 | expect(messagesListener).toHaveBeenCalledWith({
148 | messages: [{ type: 'warning' }],
149 | reset: false
150 | })
151 | expect(messagesListener).toHaveBeenCalledWith({
152 | messages: [{ type: 'error' }],
153 | reset: false
154 | })
155 | })
156 |
157 | it('verifies warning and info messages filtered when logging level set to error', () => {
158 | initialize('error')
159 |
160 | expect(messagesListener).not.toHaveBeenCalledWith({
161 | messages: [{ type: 'info' }],
162 | reset: false
163 | })
164 | expect(messagesListener).not.toHaveBeenCalledWith({
165 | messages: [{ type: 'warning' }],
166 | reset: false
167 | })
168 | expect(messagesListener).toHaveBeenCalledWith({
169 | messages: [{ type: 'error' }],
170 | reset: false
171 | })
172 | })
173 |
174 | it('verifies a new message list is sent when the logging level is changed', () => {
175 | initialize('info')
176 |
177 | atom.config.set('latex.loggingLevel', 'error')
178 |
179 | expect(messagesListener).toHaveBeenCalledWith({
180 | messages: [{ type: 'error' }],
181 | reset: true
182 | })
183 | })
184 | })
185 |
186 | describe('setMessages', () => {
187 | it('replaces message list and sends reset signal when called', () => {
188 | const messages = [{ type: 'error', text: 'foo' }]
189 |
190 | initialize('info')
191 | logger.setMessages(messages)
192 |
193 | expect(messagesListener).toHaveBeenCalledWith({ messages, reset: true })
194 | expect(logger.getMessages(false)).toEqual(messages)
195 | })
196 | })
197 |
198 | describe('clear', () => {
199 | it('empties message list and sends reset signal when called', () => {
200 | initialize('info')
201 | logger.clear()
202 |
203 | expect(messagesListener).toHaveBeenCalledWith({ messages: [], reset: true })
204 | expect(logger.getMessages(false)).toEqual([])
205 | })
206 | })
207 |
208 | describe('refresh', () => {
209 | it('sends reset signal when called', () => {
210 | const messages = [{ type: 'error' }]
211 |
212 | initialize('error')
213 | logger.refresh()
214 |
215 | expect(messagesListener).toHaveBeenCalledWith({ messages, reset: true })
216 | })
217 | })
218 | })
219 |
--------------------------------------------------------------------------------
/spec/marker-manager-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 | import { activatePackages } from './spec-helpers'
6 |
7 | import MarkerManager from '../lib/marker-manager'
8 |
9 | describe('MarkerManager', () => {
10 | beforeEach(async () => {
11 | await activatePackages()
12 | })
13 |
14 | describe('addMarkers', () => {
15 | let manager
16 |
17 | beforeEach(() => {
18 | const editor = {
19 | getPath: () => 'foo.tex',
20 | onDidDestroy: () => ({ dispose: () => {} })
21 | }
22 | manager = new MarkerManager(editor)
23 | spyOn(manager, 'addMarker')
24 | spyOn(manager, 'clear')
25 | })
26 |
27 | it('verifies that only messages that have a range and a matching file path are marked', () => {
28 | const messages = [
29 | { type: 'error', range: [[0, 0], [0, 1]], filePath: 'foo.tex' },
30 | { type: 'warning', range: [[0, 0], [0, 1]], filePath: 'bar.tex' },
31 | { type: 'info', filePath: 'foo.tex' }
32 | ]
33 | manager.addMarkers(messages, false)
34 |
35 | expect(manager.addMarker).toHaveBeenCalledWith('error', 'foo.tex', [[0, 0], [0, 1]])
36 | expect(manager.addMarker.calls.length).toEqual(1)
37 | expect(manager.clear).not.toHaveBeenCalled()
38 | })
39 |
40 | it('verifies that clear is called when reset flag is set', () => {
41 | manager.addMarkers([], true)
42 |
43 | expect(manager.clear).toHaveBeenCalled()
44 | })
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/spec/opener-registry-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 | import { activatePackages } from './spec-helpers'
6 |
7 | describe('OpenerRegistry', () => {
8 | const filePath = 'wibble.pdf'
9 | // The various viewers
10 | let cannotOpen, canOpen, canOpenInBackground, canOpenWithSynctex
11 |
12 | beforeEach(async () => {
13 | await activatePackages()
14 | })
15 |
16 | function createOpener (name, canOpen, hasSynctex, canOpenInBackground) {
17 | const opener = jasmine.createSpyObj(name, [
18 | 'open',
19 | 'canOpen',
20 | 'hasSynctex',
21 | 'canOpenInBackground'
22 | ])
23 |
24 | opener.open.andCallFake(() => Promise.resolve())
25 | opener.canOpen.andReturn(canOpen)
26 | opener.hasSynctex.andReturn(hasSynctex)
27 | opener.canOpenInBackground.andReturn(canOpenInBackground)
28 |
29 | latex.opener.openers.set(name, opener)
30 |
31 | return opener
32 | }
33 |
34 | beforeEach(() => {
35 | latex.opener.openers.clear()
36 |
37 | // The opener names have to conform to latex.opener schema
38 | cannotOpen = createOpener('skim', false, true, true)
39 | canOpen = createOpener('xdg-open', true, false, false)
40 | canOpenInBackground = createOpener('okular', true, false, true)
41 | canOpenWithSynctex = createOpener('evince', true, true, false)
42 | })
43 |
44 | describe('open', () => {
45 | it('opens using preferred viewer even if it does not have requested features', async () => {
46 | atom.config.set('latex.enableSynctex', true)
47 | atom.config.set('latex.openResultInBackground', true)
48 | atom.config.set('latex.opener', 'xdg-open')
49 |
50 | await latex.opener.open(filePath)
51 |
52 | expect(cannotOpen.open).not.toHaveBeenCalled()
53 | expect(canOpen.open).toHaveBeenCalled()
54 | expect(canOpenInBackground.open).not.toHaveBeenCalled()
55 | expect(canOpenWithSynctex.open).not.toHaveBeenCalled()
56 | })
57 |
58 | it('opens viewer that supports SyncTeX when enabled', async () => {
59 | atom.config.set('latex.enableSynctex', true)
60 | atom.config.set('latex.openResultInBackground', true)
61 | atom.config.set('latex.opener', 'automatic')
62 |
63 | await latex.opener.open(filePath)
64 |
65 | expect(cannotOpen.open).not.toHaveBeenCalled()
66 | expect(canOpen.open).not.toHaveBeenCalled()
67 | expect(canOpenInBackground.open).not.toHaveBeenCalled()
68 | expect(canOpenWithSynctex.open).toHaveBeenCalled()
69 | })
70 |
71 | it('opens viewer that supports background opening when enabled', async () => {
72 | atom.config.set('latex.enableSynctex', false)
73 | atom.config.set('latex.openResultInBackground', true)
74 | atom.config.set('latex.opener', 'automatic')
75 |
76 | await latex.opener.open(filePath)
77 |
78 | expect(cannotOpen.open).not.toHaveBeenCalled()
79 | expect(canOpen.open).not.toHaveBeenCalled()
80 | expect(canOpenInBackground.open).toHaveBeenCalled()
81 | expect(canOpenWithSynctex.open).not.toHaveBeenCalled()
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/spec/parsers/fdb-parser-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from '../async-spec-helpers'
5 |
6 | import path from 'path'
7 | import FdbParser from '../../lib/parsers/fdb-parser'
8 |
9 | describe('FdbParser', () => {
10 | let fixturesPath, fdbFile, texFile
11 |
12 | beforeEach(() => {
13 | fixturesPath = atom.project.getPaths()[0]
14 | fdbFile = path.join(fixturesPath, 'log-parse', 'file-pdfps.fdb_latexmk')
15 | texFile = path.join(fixturesPath, 'file.tex')
16 | })
17 |
18 | describe('parse', () => {
19 | it('returns the expected parsed fdb', () => {
20 | const parser = new FdbParser(fdbFile, texFile)
21 | const result = parser.parse()
22 | const expectedResult = {
23 | dvips: {
24 | source: ['log-parse/file-pdfps.dvi'],
25 | generated: ['log-parse/file-pdfps.ps']
26 | },
27 | latex: {
28 | source: ['file-pdfps.aux', 'file.tex'],
29 | generated: ['log-parse/file-pdfps.aux', 'log-parse/file-pdfps.log', 'log-parse/file-pdfps.dvi']
30 | },
31 | ps2pdf: {
32 | source: ['log-parse/file-pdfps.ps'],
33 | generated: ['log-parse/file-pdfps.pdf']
34 | }
35 | }
36 |
37 | expect(result).toEqual(expectedResult)
38 | })
39 | })
40 |
41 | describe('getLines', () => {
42 | it('returns the expected number of lines', () => {
43 | const parser = new FdbParser(fdbFile, texFile)
44 | const lines = parser.getLines()
45 |
46 | expect(lines.length).toBe(17)
47 | })
48 |
49 | it('throws an error when passed a filepath that does not exist', () => {
50 | const fdbFile = path.join(fixturesPath, 'nope.fdb_latexmk')
51 | const texFile = path.join(fixturesPath, 'nope.tex')
52 | const parser = new FdbParser(fdbFile, texFile)
53 |
54 | expect(parser.getLines).toThrow()
55 | })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/spec/parsers/log-parser-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from '../async-spec-helpers'
5 |
6 | import _ from 'lodash'
7 | import path from 'path'
8 | import LogParser from '../../lib/parsers/log-parser'
9 |
10 | describe('LogParser', () => {
11 | let fixturesPath
12 |
13 | beforeEach(() => {
14 | fixturesPath = atom.project.getPaths()[0]
15 | })
16 |
17 | describe('parse', () => {
18 | it('returns the expected output path', () => {
19 | const expectedPath = path.resolve('/foo/output/file.pdf')
20 | const logFile = path.join(fixturesPath, 'file.log')
21 | const texFile = path.join(fixturesPath, 'file.tex')
22 | const parser = new LogParser(logFile, texFile)
23 | const result = parser.parse()
24 |
25 | expect(result.outputFilePath).toBe(expectedPath)
26 | })
27 |
28 | it('returns the expected output path when the compiled file contained spaces', () => {
29 | const expectedPath = path.resolve('/foo/output/filename with spaces.pdf')
30 | const logFile = path.join(fixturesPath, 'filename with spaces.log')
31 | const texFile = path.join(fixturesPath, 'filename with spaces.tex')
32 | const parser = new LogParser(logFile, texFile)
33 | const result = parser.parse()
34 |
35 | expect(result.outputFilePath).toBe(expectedPath)
36 | })
37 |
38 | it('parses and returns all errors', () => {
39 | const logFile = path.join(fixturesPath, 'errors.log')
40 | const texFile = path.join(fixturesPath, 'errors.tex')
41 | const parser = new LogParser(logFile, texFile)
42 | const result = parser.parse()
43 |
44 | expect(_.countBy(result.messages, 'type').error).toBe(3)
45 | })
46 |
47 | it('associates an error with a file path, line number, and message', () => {
48 | const logFile = path.join(fixturesPath, 'errors.log')
49 | const texFile = path.join(fixturesPath, 'errors.tex')
50 | const parser = new LogParser(logFile, texFile)
51 | const result = parser.parse()
52 | const error = result.messages.find(message => { return message.type === 'error' })
53 |
54 | expect(error).toEqual({
55 | type: 'error',
56 | logRange: [[196, 0], [196, 84]],
57 | filePath: texFile,
58 | range: [[9, 0], [9, Number.MAX_SAFE_INTEGER]],
59 | logPath: logFile,
60 | text: '\\begin{gather*} on input line 8 ended by \\end{gather}'
61 | })
62 | })
63 | })
64 |
65 | describe('getLines', () => {
66 | it('returns the expected number of lines', () => {
67 | const logFile = path.join(fixturesPath, 'file.log')
68 | const texFile = path.join(fixturesPath, 'file.tex')
69 | const parser = new LogParser(logFile, texFile)
70 | const lines = parser.getLines()
71 |
72 | expect(lines.length).toBe(63)
73 | })
74 |
75 | it('throws an error when passed a filepath that does not exist', () => {
76 | const logFile = path.join(fixturesPath, 'nope.log')
77 | const texFile = path.join(fixturesPath, 'nope.tex')
78 | const parser = new LogParser(logFile, texFile)
79 |
80 | expect(parser.getLines).toThrow()
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/spec/parsers/magic-parser-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from '../async-spec-helpers'
5 |
6 | import path from 'path'
7 | import MagicParser from '../../lib/parsers/magic-parser'
8 |
9 | describe('MagicParser', () => {
10 | let fixturesPath
11 |
12 | beforeEach(() => {
13 | fixturesPath = atom.project.getPaths()[0]
14 | })
15 |
16 | describe('parse', () => {
17 | it('returns an empty object when file contains no magic comments', () => {
18 | const filePath = path.join(fixturesPath, 'file.tex')
19 | const parser = new MagicParser(filePath)
20 | const result = parser.parse()
21 |
22 | expect(result).toEqual({})
23 | })
24 |
25 | it('returns path to root file when file contains magic root comment', () => {
26 | const filePath = path.join(fixturesPath, 'magic-comments', 'root-comment.tex')
27 | const parser = new MagicParser(filePath)
28 | const result = parser.parse()
29 |
30 | expect(result).toEqual({
31 | 'root': '../file.tex'
32 | })
33 | })
34 |
35 | it('returns path to root file when file contains magic root comment when magic comment is not on the first line', () => {
36 | const filePath = path.join(fixturesPath, 'magic-comments', 'not-first-line.tex')
37 | const parser = new MagicParser(filePath)
38 | const result = parser.parse()
39 |
40 | expect(result).toEqual({
41 | 'root': '../file.tex'
42 | })
43 | })
44 |
45 | it('handles magic comments without optional whitespace', () => {
46 | const filePath = path.join(fixturesPath, 'magic-comments', 'no-whitespace.tex')
47 | const parser = new MagicParser(filePath)
48 | const result = parser.parse()
49 |
50 | expect(result).not.toEqual({})
51 | })
52 | it('detects multiple object information when multiple magice comments are defined', () => {
53 | const filePath = path.join(fixturesPath, 'magic-comments', 'multiple-magic-comments.tex')
54 | const parser = new MagicParser(filePath)
55 | const result = parser.parse()
56 |
57 | expect(result).toEqual({
58 | 'root': '../file.tex',
59 | 'program': 'lualatex'
60 | })
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/spec/process-manager-spec.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | // eslint-disable-next-line no-unused-vars
4 | import { afterEach, beforeEach, it, fit } from './async-spec-helpers'
5 |
6 | import fs from 'fs-plus'
7 | import path from 'path'
8 | import temp from 'temp'
9 | import ProcessManager from '../lib/process-manager'
10 |
11 | describe('ProcessManager', () => {
12 | let processManager
13 |
14 | function constructCommand (fileName) {
15 | const tempPath = fs.realpathSync(temp.mkdirSync('latex'))
16 | const filePath = path.join(tempPath, fileName)
17 | return `latexmk -cd -f -pdf "${filePath}"`
18 | }
19 |
20 | beforeEach(() => {
21 | processManager = new ProcessManager()
22 | })
23 |
24 | it('kills latexmk when given non-existant file', () => {
25 | let killed = false
26 |
27 | processManager.executeChildProcess(constructCommand('foo.tex'), { allowKill: true }).then(result => { killed = true })
28 | processManager.killChildProcesses()
29 |
30 | waitsFor(() => killed, 5000)
31 | })
32 |
33 | it('kills old latexmk instances, but not ones created after the kill command', () => {
34 | let oldKilled = false
35 | let newKilled = false
36 |
37 | processManager.executeChildProcess(constructCommand('old.tex'), { allowKill: true }).then(result => { oldKilled = true })
38 | processManager.killChildProcesses()
39 | processManager.executeChildProcess(constructCommand('new.tex'), { allowKill: true }).then(result => { newKilled = true })
40 |
41 | waitsFor(() => oldKilled, 5000)
42 |
43 | runs(() => {
44 | expect(newKilled).toBe(false)
45 | processManager.killChildProcesses()
46 | })
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/spec/spec-helpers.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import fs from 'fs-plus'
4 | import temp from 'temp'
5 | import wrench from 'wrench'
6 |
7 | export default {
8 | cloneFixtures () {
9 | const tempPath = fs.realpathSync(temp.mkdirSync('latex'))
10 | let fixturesPath = atom.project.getPaths()[0]
11 | wrench.copyDirSyncRecursive(fixturesPath, tempPath, {forceDelete: true})
12 | atom.project.setPaths([tempPath])
13 | fixturesPath = tempPath
14 |
15 | return fixturesPath
16 | },
17 |
18 | overridePlatform (name) {
19 | Object.defineProperty(process, 'platform', {__proto__: null, value: name})
20 | },
21 |
22 | setTimeoutInterval (interval) {
23 | const env = jasmine.getEnv()
24 | const originalInterval = env.defaultTimeoutInterval
25 | env.defaultTimeoutInterval = interval
26 |
27 | return originalInterval
28 | },
29 |
30 | activatePackages () {
31 | const workspaceElement = atom.views.getView(atom.workspace)
32 | const packages = ['language-latex', 'pdf-view', 'latex']
33 | const activationPromise = Promise.all(packages.map(pkg => atom.packages.activatePackage(pkg)))
34 | atom.commands.dispatch(workspaceElement, 'latex:sync')
35 | return activationPromise
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spec/stubs.js:
--------------------------------------------------------------------------------
1 | /** @babel */
2 |
3 | import Builder from '../lib/builder'
4 | import Logger from '../lib/logger'
5 | import Opener from '../lib/opener'
6 |
7 | export class NullBuilder extends Builder {
8 | static extension = '.tex'
9 | static canProcess (filePath) { return filePath.endsWith(NullBuilder.extension) }
10 | }
11 |
12 | export class NullLogger extends Logger {
13 | error () {}
14 | warning () {}
15 | info () {}
16 | }
17 |
18 | export class NullOpener extends Opener {
19 | open () {}
20 | }
21 |
--------------------------------------------------------------------------------
/styles/latex.less:
--------------------------------------------------------------------------------
1 | @import "ui-variables";
2 |
3 | status-bar {
4 | .latex-status {
5 | .latex-message-count {
6 | padding-left: 0.5em;
7 | }
8 |
9 | .latex-highlight {
10 | color: @text-color-highlight;
11 | }
12 |
13 | .latex-info {
14 | color: @text-color-info;
15 | }
16 |
17 | .latex-warning {
18 | color: @text-color-warning;
19 | }
20 |
21 | .latex-error {
22 | color: @text-color-error;
23 | }
24 |
25 | a {
26 | cursor: pointer;
27 | }
28 |
29 | .icon {
30 | padding-right: 0.125em;
31 | display: inline-block;
32 | }
33 |
34 | &.is-busy {
35 | color: @text-color-highlight;
36 |
37 | a {
38 | color: @text-color-highlight;
39 | }
40 |
41 | .busy.icon::before {
42 | -webkit-animation: spin 2s infinite linear;
43 | text-align: center;
44 | }
45 | }
46 | }
47 | }
48 |
49 | .latex-log {
50 | flex: 1 1 auto;
51 | display: flex;
52 | flex-direction: column;
53 | padding: 0.375em;
54 |
55 | .log-block {
56 | padding: 0.375em;
57 | flex: 0 1 auto;
58 | }
59 |
60 | .log-block.expand {
61 | border: 1px solid @base-border-color;
62 | overflow-y: scroll;
63 | flex: 1 1 auto;
64 | }
65 |
66 | table {
67 | width: 100%;
68 | }
69 |
70 | th {
71 | background-color: @base-background-color;
72 | border: 1px solid @base-border-color;
73 | }
74 |
75 | td {
76 | border: 1px none;
77 | }
78 |
79 | td, th {
80 | vertical-align: top;
81 | padding: .05em .25em .05em .25em;
82 | }
83 |
84 | .latex-info .icon {
85 | color: @text-color-info;
86 | }
87 |
88 | .latex-warning .icon {
89 | color: @text-color-warning;
90 | }
91 |
92 | .latex-error .icon {
93 | color: @text-color-error;
94 | }
95 |
96 | .panel-body {
97 | margin-top: 5px;
98 | overflow-y: scroll;
99 | }
100 |
101 | a.latex-file-reference {
102 | white-space: nowrap;
103 | color: @text-color-subtle;
104 | }
105 |
106 | .latex-highlight {
107 | color: @text-color-highlight;
108 | }
109 | }
110 |
111 | @-webkit-keyframes spin {
112 | from {
113 | -webkit-transform: rotate(0deg);
114 | }
115 | to {
116 | -webkit-transform: rotate(360deg);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/styles/markers.atom-text-editor.less:
--------------------------------------------------------------------------------
1 | @import "ui-variables";
2 |
3 | atom-text-editor .gutter .line-number {
4 | /* Use CSS specificity to force errors to override warnings and info markers */
5 | &.latex-error, &.latex-error.latex-warning, &.latex-error.latex-info,
6 | &.latex-error.latex-warning.latex-info {
7 | border-left: 2px solid @background-color-error;
8 | padding-left: ~"calc(0.5em - 2px)";
9 | }
10 | /* Use CSS specificity to force warnings to override info markes. */
11 | &.latex-warning, &.latex-warning.latex-info {
12 | border-left: 2px solid @background-color-warning;
13 | padding-left: ~"calc(0.5em - 2px)";
14 | }
15 | &.latex-info {
16 | border-left: 2px solid @background-color-info;
17 | padding-left: ~"calc(0.5em - 2px)";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------