Merge pull request #4893 from overleaf/em-synctex

Use the synctex distributed with TeX Live

GitOrigin-RevId: 5a133f21f48fd1e217ab463e8cb2a5cdec8be1af
This commit is contained in:
Eric Mc Sween 2021-09-07 08:57:47 -04:00 committed by Copybot
parent fa2567aa61
commit 9ee92daea3
27 changed files with 380 additions and 5667 deletions

View file

@ -81,7 +81,6 @@ services:
# SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true' # SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true'
# SANDBOXED_COMPILES_HOST_DIR: '/var/sharelatex_data/data/compiles' # SANDBOXED_COMPILES_HOST_DIR: '/var/sharelatex_data/data/compiles'
# SYNCTEX_BIN_HOST_PATH: '/var/sharelatex_data/bin/synctex'
# DOCKER_RUNNER: 'false' # DOCKER_RUNNER: 'false'

View file

@ -26,11 +26,6 @@ RUN node genScript install | bash
# -------------------- # --------------------
RUN node genScript compile | bash RUN node genScript compile | bash
# Links CLSI synctex to its default location
# ------------------------------------------
RUN ln -s /var/www/sharelatex/clsi/bin/synctex /opt/synctex
# Copy runit service startup scripts to its location # Copy runit service startup scripts to its location
# -------------------------------------------------- # --------------------------------------------------
ADD server-ce/runit /etc/service ADD server-ce/runit /etc/service

View file

@ -61,6 +61,7 @@ RUN mkdir /install-tl-unx \
&& tlmgr install --repository ${TEXLIVE_MIRROR} \ && tlmgr install --repository ${TEXLIVE_MIRROR} \
latexmk \ latexmk \
texcount \ texcount \
synctex \
\ \
&& rm -rf /install-tl-unx && rm -rf /install-tl-unx

View file

@ -15,17 +15,4 @@ if [ -e '/var/run/docker.sock' ]; then
usermod -aG dockeronhost www-data usermod -aG dockeronhost www-data
fi fi
# Copies over CSLI synctex to the host mounted volume, so it
# can be subsequently mounted in TexLive containers on Sandbox Compilation
SYNCTEX=/var/lib/sharelatex/bin/synctex
if [ ! -f "$SYNCTEX" ]; then
if [ "$DISABLE_SYNCTEX_BINARY_COPY" == "true" ]; then
echo ">> Copy of synctex executable disabled by DISABLE_SYNCTEX_BINARY_COPY flag, feature may not work"
else
echo ">> Copying synctex executable to the host"
mkdir -p $(dirname $SYNCTEX )
cp /var/www/sharelatex/clsi/bin/synctex $SYNCTEX
fi
fi
exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/clsi/app.js >> /var/log/sharelatex/clsi.log 2>&1 exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/clsi/app.js >> /var/log/sharelatex/clsi.log 2>&1

View file

@ -31,7 +31,6 @@ The CLSI can be configured through the following environment variables:
* `SENTRY_DSN` - Sentry [Data Source Name](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) * `SENTRY_DSN` - Sentry [Data Source Name](https://docs.sentry.io/product/sentry-basics/dsn-explainer/)
* `SMOKE_TEST` - Whether to run smoke tests * `SMOKE_TEST` - Whether to run smoke tests
* `SQLITE_PATH` - Path to SQLite database * `SQLITE_PATH` - Path to SQLite database
* `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary
* `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` * `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1`
* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `gcr.io/overleaf-ops` * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `gcr.io/overleaf-ops`
* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex` * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex`

View file

@ -17,6 +17,7 @@ const async = require('async')
const Errors = require('./Errors') const Errors = require('./Errors')
const CommandRunner = require('./CommandRunner') const CommandRunner = require('./CommandRunner')
const { emitPdfStats } = require('./ContentCacheMetrics') const { emitPdfStats } = require('./ContentCacheMetrics')
const SynctexOutputParser = require('./SynctexOutputParser')
function getCompileName(projectId, userId) { function getCompileName(projectId, userId) {
if (userId != null) { if (userId != null) {
@ -428,32 +429,44 @@ function syncFromCode(
// wherever it is on the host. // wherever it is on the host.
const compileName = getCompileName(projectId, userId) const compileName = getCompileName(projectId, userId)
const baseDir = Settings.path.synctexBaseDir(compileName) const baseDir = Settings.path.synctexBaseDir(compileName)
const filePath = baseDir + '/' + filename const inputFilePath = Path.join(baseDir, filename)
const synctexPath = `${baseDir}/output.pdf` const outputFilePath = Path.join(baseDir, 'output.pdf')
const command = ['code', synctexPath, filePath, line, column] const command = [
'synctex',
'view',
'-i',
`${line}:${column}:${inputFilePath}`,
'-o',
outputFilePath,
]
_runSynctex(projectId, userId, command, imageName, (error, stdout) => { _runSynctex(projectId, userId, command, imageName, (error, stdout) => {
if (error) { if (error) {
return callback(error) return callback(error)
} }
logger.log( logger.debug(
{ projectId, userId, filename, line, column, command, stdout }, { projectId, userId, filename, line, column, command, stdout },
'synctex code output' 'synctex code output'
) )
callback(null, _parseSynctexFromCodeOutput(stdout)) callback(null, SynctexOutputParser.parseViewOutput(stdout))
}) })
} }
function syncFromPdf(projectId, userId, page, h, v, imageName, callback) { function syncFromPdf(projectId, userId, page, h, v, imageName, callback) {
const compileName = getCompileName(projectId, userId) const compileName = getCompileName(projectId, userId)
const baseDir = Settings.path.synctexBaseDir(compileName) const baseDir = Settings.path.synctexBaseDir(compileName)
const synctexPath = `${baseDir}/output.pdf` const outputFilePath = `${baseDir}/output.pdf`
const command = ['pdf', synctexPath, page, h, v] const command = [
'synctex',
'edit',
'-o',
`${page}:${h}:${v}:${outputFilePath}`,
]
_runSynctex(projectId, userId, command, imageName, (error, stdout) => { _runSynctex(projectId, userId, command, imageName, (error, stdout) => {
if (error != null) { if (error != null) {
return callback(error) return callback(error)
} }
logger.log({ projectId, userId, page, h, v, stdout }, 'synctex pdf output') logger.log({ projectId, userId, page, h, v, stdout }, 'synctex pdf output')
callback(null, _parseSynctexFromPdfOutput(stdout, baseDir)) callback(null, SynctexOutputParser.parseEditOutput(stdout, baseDir))
}) })
} }
@ -482,12 +495,12 @@ function _checkFileExists(dir, filename, callback) {
} }
function _runSynctex(projectId, userId, command, imageName, callback) { function _runSynctex(projectId, userId, command, imageName, callback) {
command.unshift('/opt/synctex')
const directory = getCompileDir(projectId, userId) const directory = getCompileDir(projectId, userId)
const timeout = 60 * 1000 // increased to allow for large projects const timeout = 60 * 1000 // increased to allow for large projects
const compileName = getCompileName(projectId, userId) const compileName = getCompileName(projectId, userId)
const compileGroup = 'synctex' const compileGroup = 'synctex'
const defaultImageName =
Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.image
_checkFileExists(directory, 'output.synctex.gz', error => { _checkFileExists(directory, 'output.synctex.gz', error => {
if (error) { if (error) {
return callback(error) return callback(error)
@ -496,10 +509,7 @@ function _runSynctex(projectId, userId, command, imageName, callback) {
compileName, compileName,
command, command,
directory, directory,
imageName || imageName || defaultImageName,
(Settings.clsi && Settings.clsi.docker
? Settings.clsi.docker.image
: undefined),
timeout, timeout,
{}, {},
compileGroup, compileGroup,
@ -517,39 +527,6 @@ function _runSynctex(projectId, userId, command, imageName, callback) {
}) })
} }
function _parseSynctexFromCodeOutput(output) {
const results = []
for (const line of output.split('\n')) {
const [node, page, h, v, width, height] = line.split('\t')
if (node === 'NODE') {
results.push({
page: parseInt(page, 10),
h: parseFloat(h),
v: parseFloat(v),
height: parseFloat(height),
width: parseFloat(width),
})
}
}
return results
}
function _parseSynctexFromPdfOutput(output, baseDir) {
const results = []
for (const line of output.split('\n')) {
const [node, filePath, lineNum, column] = line.split('\t')
if (node === 'NODE') {
const file = filePath.slice(baseDir.length + 1)
results.push({
file,
line: parseInt(lineNum, 10),
column: parseInt(column, 10),
})
}
}
return results
}
function wordcount(projectId, userId, filename, image, callback) { function wordcount(projectId, userId, filename, image, callback) {
logger.log({ projectId, userId, filename, image }, 'running wordcount') logger.log({ projectId, userId, filename, image }, 'running wordcount')
const filePath = `$COMPILE_DIR/${filename}` const filePath = `$COMPILE_DIR/${filename}`

View file

@ -258,12 +258,6 @@ const DockerRunner = {
}, },
} }
if (Settings.path != null && Settings.path.synctexBinHostPath != null) {
options.HostConfig.Binds.push(
`${Settings.path.synctexBinHostPath}:/opt/synctex:ro`
)
}
if (Settings.clsi.docker.seccomp_profile != null) { if (Settings.clsi.docker.seccomp_profile != null) {
options.HostConfig.SecurityOpt.push( options.HostConfig.SecurityOpt.push(
`seccomp=${Settings.clsi.docker.seccomp_profile}` `seccomp=${Settings.clsi.docker.seccomp_profile}`

View file

@ -0,0 +1,113 @@
const Path = require('path')
/**
* Parse output from the `synctex view` command
*/
function parseViewOutput(output) {
return _parseOutput(output, (record, label, value) => {
switch (label) {
case 'Page':
_setIntProp(record, 'page', value)
break
case 'h':
_setFloatProp(record, 'h', value)
break
case 'v':
_setFloatProp(record, 'v', value)
break
case 'W':
_setFloatProp(record, 'width', value)
break
case 'H':
_setFloatProp(record, 'height', value)
break
}
})
}
/**
* Parse output from the `synctex edit` command
*/
function parseEditOutput(output, baseDir) {
return _parseOutput(output, (record, label, value) => {
switch (label) {
case 'Input':
if (Path.isAbsolute(value)) {
record.file = Path.relative(baseDir, value)
} else {
record.file = value
}
break
case 'Line':
_setIntProp(record, 'line', value)
break
case 'Column':
_setIntProp(record, 'column', value)
break
}
})
}
/**
* Generic parser for synctex output
*
* Parses the output into records. Each line is split into a label and a value,
* which are then sent to `processLine` for further processing.
*/
function _parseOutput(output, processLine) {
const lines = output.split('\n')
let currentRecord = null
const records = []
for (const line of lines) {
const [label, value] = _splitLine(line)
// A line that starts with 'Output:' indicates a new record
if (label === 'Output') {
// Start new record
currentRecord = {}
records.push(currentRecord)
continue
}
// Ignore the line if we're not in a record yet
if (currentRecord == null) {
continue
}
// Process the line
processLine(currentRecord, label, value)
}
return records
}
/**
* Split a line in label and value components.
*
* The components are separated by a colon. Note that this is slightly
* different from `line.split(':', 2)`. This version puts the entirety of the
* line after the colon in the value component, even if there are more colons
* on the line.
*/
function _splitLine(line) {
const splitIndex = line.indexOf(':')
if (splitIndex === -1) {
return ['', line]
}
return [line.slice(0, splitIndex).trim(), line.slice(splitIndex + 1).trim()]
}
function _setIntProp(record, prop, value) {
const intValue = parseInt(value, 10)
if (!isNaN(intValue)) {
record[prop] = intValue
}
}
function _setFloatProp(record, prop, value) {
const floatValue = parseFloat(value)
if (!isNaN(floatValue)) {
record[prop] = floatValue
}
}
module.exports = { parseViewOutput, parseEditOutput }

Binary file not shown.

View file

@ -1,5 +0,0 @@
#!/bin/bash
echo "hello world"
sleep 3
echo "awake"
/opt/synctex pdf /compile/output.pdf 1 100 200

View file

@ -9,12 +9,10 @@ services:
SHARELATEX_CONFIG: /app/config/settings.defaults.js SHARELATEX_CONFIG: /app/config/settings.defaults.js
DOCKER_RUNNER: "true" DOCKER_RUNNER: "true"
COMPILES_HOST_DIR: $PWD/compiles COMPILES_HOST_DIR: $PWD/compiles
SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./compiles:/app/compiles - ./compiles:/app/compiles
- ./cache:/app/cache - ./cache:/app/cache
- ./bin/synctex:/app/bin/synctex
ci: ci:
@ -25,10 +23,8 @@ services:
SHARELATEX_CONFIG: /app/config/settings.defaults.js SHARELATEX_CONFIG: /app/config/settings.defaults.js
DOCKER_RUNNER: "true" DOCKER_RUNNER: "true"
COMPILES_HOST_DIR: $PWD/compiles COMPILES_HOST_DIR: $PWD/compiles
SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex
SQLITE_PATH: /app/compiles/db.sqlite SQLITE_PATH: /app/compiles/db.sqlite
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:rw - /var/run/docker.sock:/var/run/docker.sock:rw
- ./compiles:/app/compiles - ./compiles:/app/compiles
- ./cache:/app/cache - ./cache:/app/cache
- ./bin/synctex:/app/bin/synctex

View file

@ -13,7 +13,4 @@ mkdir -p /app/compiles && chown node:node /app/compiles
mkdir -p /app/db && chown node:node /app/db mkdir -p /app/db && chown node:node /app/db
mkdir -p /app/output && chown node:node /app/output mkdir -p /app/output && chown node:node /app/output
# make synctex available for remount in compiles
cp /app/bin/synctex /app/bin/synctex-mount/synctex
exec runuser -u node -- "$@" exec runuser -u node -- "$@"

View file

@ -6016,6 +6016,12 @@
} }
} }
}, },
"sinon-chai": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz",
"integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==",
"dev": true
},
"slice-ansi": { "slice-ansi": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",

View file

@ -60,6 +60,7 @@
"prettier": "^2.2.1", "prettier": "^2.2.1",
"sandboxed-module": "^2.0.3", "sandboxed-module": "^2.0.3",
"sinon": "~9.0.1", "sinon": "~9.0.1",
"sinon-chai": "^3.7.0",
"timekeeper": "2.2.0" "timekeeper": "2.2.0"
} }
} }

View file

@ -1,66 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "synctex/synctex_parser.h"
void print_usage() {
fprintf (stderr, "Usage: synctex code <synctex_file> <filename> <line> <column>\n");
fprintf (stderr, " synctex pdf <synctex_file> <page> <h> <v>\n");
}
int main(int argc, char *argv[], char *envp[]) {
synctex_scanner_t scanner;
if (argc < 6 || (strcmp(argv[1], "code") != 0 && strcmp(argv[1], "pdf") != 0)) {
print_usage();
return EXIT_FAILURE;
}
const char* direction = argv[1];
const char* synctex_file = argv[2];
scanner = synctex_scanner_new_with_output_file(synctex_file, NULL, 1);
if(!(scanner = synctex_scanner_parse(scanner))) {
fprintf (stderr, "Could not parse output file\n");
return EXIT_FAILURE;
}
if (strcmp(direction, "code") == 0) {
const char* name = argv[3];
int line = atoi(argv[4]);
int column = atoi(argv[5]);
if(synctex_display_query(scanner, name, line, column) > 0) {
synctex_node_t node;
while((node = synctex_next_result(scanner))) {
int page = synctex_node_page(node);
float h = synctex_node_box_visible_h(node);
float v = synctex_node_box_visible_v(node);
float width = synctex_node_box_visible_width(node);
float height = synctex_node_box_visible_height(node);
printf ("NODE\t%d\t%.2f\t%.2f\t%.2f\t%.2f\n", page, h, v, width, height);
}
}
} else if (strcmp(direction, "pdf") == 0) {
int page = atoi(argv[3]);
float h = atof(argv[4]);
float v = atof(argv[5]);
if(synctex_edit_query(scanner, page, h, v) > 0) {
synctex_node_t node;
while((node = synctex_next_result(scanner))) {
int tag = synctex_node_tag(node);
const char* name = synctex_scanner_get_name(scanner, tag);
int line = synctex_node_line(node);
int column = synctex_node_column(node);
printf ("NODE\t%s\t%d\t%d\n", name, line, column);
}
}
}
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -1,346 +0,0 @@
/*
Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the SyncTeX package.
Latest Revision: Tue Jun 14 08:23:30 UTC 2011
Version: 1.16
See synctex_parser_readme.txt for more details
License:
--------
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
Acknowledgments:
----------------
The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
and significant help from XeTeX developer Jonathan Kew
Nota Bene:
----------
If you include or use a significant part of the synctex package into a software,
I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
Version 1
Thu Jun 19 09:39:21 UTC 2008
*/
#ifndef __SYNCTEX_PARSER__
# define __SYNCTEX_PARSER__
#ifdef __cplusplus
extern "C" {
#endif
/* synctex_node_t is the type for all synctex nodes.
* The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */
typedef struct _synctex_node * synctex_node_t;
/* The main synctex object is a scanner
* Its implementation is considered private.
* The basic workflow is
* - create a "synctex scanner" with the contents of a file
* - perform actions on that scanner like display or edit queries
* - free the scanner when the work is done
*/
typedef struct __synctex_scanner_t _synctex_scanner_t;
typedef _synctex_scanner_t * synctex_scanner_t;
/* This is the designated method to create a new synctex scanner object.
* output is the pdf/dvi/xdv file associated to the synctex file.
* If necessary, it can be the tex file that originated the synctex file
* but this might cause problems if the \jobname has a custom value.
* Despite this method can accept a relative path in practice,
* you should only pass a full path name.
* The path should be encoded by the underlying file system,
* assuming that it is based on 8 bits characters, including UTF8,
* not 16 bits nor 32 bits.
* The last file extension is removed and replaced by the proper extension.
* Then the private method _synctex_scanner_new_with_contents_of_file is called.
* NULL is returned in case of an error or non existent file.
* Once you have a scanner, use the synctex_display_query and synctex_edit_query below.
* The new "build_directory" argument is available since version 1.5.
* It is the directory where all the auxiliary stuff is created.
* Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory.
* This is the case in MikTeX (I will include this into TeX Live).
* This directory path can be nil, it will be ignored then.
* It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file.
* If no synctex file is found in the same directory as the output file, then we try to find one in the build directory.
* Please note that this new "build_directory" is provided as a convenient argument but should not be used.
* In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file.
* The new "parse" argument is available since version 1.5. In general, use 1.
* Use 0 only if you do not want to parse the content but just check the existence.
*/
synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse);
/* This is the designated method to delete a synctex scanner object.
* Frees all the memory, you must call it when you are finished with the scanner.
*/
void synctex_scanner_free(synctex_scanner_t scanner);
/* Send this message to force the scanner to parse the contents of the synctex output file.
* Nothing is performed if the file was already parsed.
* In each query below, this message is sent, but if you need to access information more directly,
* you must be sure that the parsing did occur.
* Usage:
* if((my_scanner = synctex_scanner_parse(my_scanner))) {
* continue with my_scanner...
* } else {
* there was a problem
* }
*/
synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner);
/* The main entry points.
* Given the file name, a line and a column number, synctex_display_query returns the number of nodes
* satisfying the contrain. Use code like
*
* if(synctex_display_query(scanner,name,line,column)>0) {
* synctex_node_t node;
* while((node = synctex_next_result(scanner))) {
* // do something with node
* ...
* }
* }
*
* For example, one can
* - highlight each resulting node in the output, using synctex_node_h and synctex_node_v
* - highlight all the rectangles enclosing those nodes, using synctex_box_... functions
* - highlight just the character using that information
*
* Given the page and the position in the page, synctex_edit_query returns the number of nodes
* satisfying the contrain. Use code like
*
* if(synctex_edit_query(scanner,page,h,v)>0) {
* synctex_node_t node;
* while(node = synctex_next_result(scanner)) {
* // do something with node
* ...
* }
* }
*
* For example, one can
* - highlight each resulting line in the input,
* - highlight just the character using that information
*
* page is 1 based
* h and v are coordinates in 72 dpi unit, relative to the top left corner of the page.
* If you make a new query, the result of the previous one is discarded.
* If one of this function returns a non positive integer,
* it means that an error occurred.
*
* Both methods are conservative, in the sense that matching is weak.
* If the exact column number is not found, there will be an answer with the whole line.
*
* Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library.
* You can browse their code for a concrete implementation.
*/
int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column);
int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v);
synctex_node_t synctex_next_result(synctex_scanner_t scanner);
/* Display all the information contained in the scanner object.
* If the records are too numerous, only the first ones are displayed.
* This is mainly for informatinal purpose to help developers.
*/
void synctex_scanner_display(synctex_scanner_t scanner);
/* The x and y offset of the origin in TeX coordinates. The magnification
These are used by pdf viewers that want to display the real box size.
For example, getting the horizontal coordinates of a node would require
synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner)
Getting its TeX width would simply require
synctex_node_box_width(node)*synctex_scanner_magnification(scanner)
but direct methods are available for that below.
*/
int synctex_scanner_x_offset(synctex_scanner_t scanner);
int synctex_scanner_y_offset(synctex_scanner_t scanner);
float synctex_scanner_magnification(synctex_scanner_t scanner);
/* Managing the input file names.
* Given a tag, synctex_scanner_get_name will return the corresponding file name.
* Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag.
* The file name must be the very same as understood by TeX.
* For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex.
* No automatic path expansion is performed.
* Finally, synctex_scanner_input is the first input node of the scanner.
* To browse all the input node, use a loop like
*
* if((input_node = synctex_scanner_input(scanner))){
* do {
* blah
* } while((input_node=synctex_node_sibling(input_node)));
* }
*
* The output is the name that was used to create the scanner.
* The synctex is the real name of the synctex file,
* it was obtained from output by setting the proper file extension.
*/
const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag);
int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name);
synctex_node_t synctex_scanner_input(synctex_scanner_t scanner);
const char * synctex_scanner_get_output(synctex_scanner_t scanner);
const char * synctex_scanner_get_synctex(synctex_scanner_t scanner);
/* Browsing the nodes
* parent, child and sibling are standard names for tree nodes.
* The parent is one level higher, the child is one level deeper,
* and the sibling is at the same level.
* The sheet of a node is the first ancestor, it is of type sheet.
* A node and its sibling have the same parent.
* A node is the parent of its child.
* A node is either the child of its parent,
* or belongs to the sibling chain of its parent's child.
* The next node is either the child, the sibling or the parent's sibling,
* unless the parent is a sheet.
* This allows to navigate through all the nodes of a given sheet node:
*
* synctex_node_t node = sheet;
* while((node = synctex_node_next(node))) {
* // do something with node
* }
*
* With synctex_sheet_content, you can retrieve the sheet node given the page.
* The page is 1 based, according to TeX standards.
* Conversely synctex_node_sheet allows to retrieve the sheet containing a given node.
*/
synctex_node_t synctex_node_parent(synctex_node_t node);
synctex_node_t synctex_node_sheet(synctex_node_t node);
synctex_node_t synctex_node_child(synctex_node_t node);
synctex_node_t synctex_node_sibling(synctex_node_t node);
synctex_node_t synctex_node_next(synctex_node_t node);
synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page);
/* These are the types of the synctex nodes */
typedef enum {
synctex_node_type_error = 0,
synctex_node_type_input,
synctex_node_type_sheet,
synctex_node_type_vbox,
synctex_node_type_void_vbox,
synctex_node_type_hbox,
synctex_node_type_void_hbox,
synctex_node_type_kern,
synctex_node_type_glue,
synctex_node_type_math,
synctex_node_type_boundary,
synctex_node_number_of_types
} synctex_node_type_t;
/* synctex_node_type gives the type of a given node,
* synctex_node_isa gives the same information as a human readable text. */
synctex_node_type_t synctex_node_type(synctex_node_t node);
const char * synctex_node_isa(synctex_node_t node);
/* This is primarily used for debugging purpose.
* The second one logs information for the node and recursively displays information for its next node */
void synctex_node_log(synctex_node_t node);
void synctex_node_display(synctex_node_t node);
/* Given a node, access to its tag, line and column.
* The line and column numbers are 1 based.
* The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line.
* When the tag is known, the scanner of the node will give the corresponding file name.
* When the tag is known, the scanner of the node will give the name.
*/
int synctex_node_tag(synctex_node_t node);
int synctex_node_line(synctex_node_t node);
int synctex_node_column(synctex_node_t node);
/* This is the page where the node appears.
* This is a 1 based index as given by TeX.
*/
int synctex_node_page(synctex_node_t node);
/* For quite all nodes, horizontal, vertical coordinates, and width.
* These are expressed in TeX small points coordinates, with origin at the top left corner.
*/
int synctex_node_h(synctex_node_t node);
int synctex_node_v(synctex_node_t node);
int synctex_node_width(synctex_node_t node);
/* For all nodes, dimensions of the enclosing box.
* These are expressed in TeX small points coordinates, with origin at the top left corner.
* A box is enclosing itself.
*/
int synctex_node_box_h(synctex_node_t node);
int synctex_node_box_v(synctex_node_t node);
int synctex_node_box_width(synctex_node_t node);
int synctex_node_box_height(synctex_node_t node);
int synctex_node_box_depth(synctex_node_t node);
/* For quite all nodes, horizontal, vertical coordinates, and width.
* The visible dimensions are bigger than real ones to compensate 0 width boxes
* that do contain nodes.
* These are expressed in page coordinates, with origin at the top left corner.
* A box is enclosing itself.
*/
float synctex_node_visible_h(synctex_node_t node);
float synctex_node_visible_v(synctex_node_t node);
float synctex_node_visible_width(synctex_node_t node);
/* For all nodes, visible dimensions of the enclosing box.
* A box is enclosing itself.
* The visible dimensions are bigger than real ones to compensate 0 width boxes
* that do contain nodes.
*/
float synctex_node_box_visible_h(synctex_node_t node);
float synctex_node_box_visible_v(synctex_node_t node);
float synctex_node_box_visible_width(synctex_node_t node);
float synctex_node_box_visible_height(synctex_node_t node);
float synctex_node_box_visible_depth(synctex_node_t node);
/* The main synctex updater object.
* This object is used to append information to the synctex file.
* Its implementation is considered private.
* It is used by the synctex command line tool to take into account modifications
* that could occur while postprocessing files by dvipdf like filters.
*/
typedef struct __synctex_updater_t _synctex_updater_t;
typedef _synctex_updater_t * synctex_updater_t;
/* Designated initializer.
* Once you are done with your whole job,
* free the updater */
synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory);
/* Use the next functions to append records to the synctex file,
* no consistency tests made on the arguments */
void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification);
void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset);
void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset);
/* You MUST free the updater, once everything is properly appended */
void synctex_updater_free(synctex_updater_t updater);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,45 +0,0 @@
/*
Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the SyncTeX package.
Latest Revision: Tue Jun 14 08:23:30 UTC 2011
Version: 1.16
See synctex_parser_readme.txt for more details
License:
--------
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
*/
/* This local header file is for TEXLIVE, use your own header to fit your system */
# include <w2c/c-auto.h> /* for inline && HAVE_xxx */
/* No inlining for synctex tool in texlive. */
# define SYNCTEX_INLINE

View file

@ -1,141 +0,0 @@
This file is part of the SyncTeX package.
The Synchronization TeXnology named SyncTeX is a new feature
of recent TeX engines designed by Jerome Laurens.
It allows to synchronize between input and output, which means to
navigate from the source document to the typeset material and vice versa.
More informations on http://itexmac2.sourceforge.net/SyncTeX.html
This package is mainly for developers, it mainly contains the following files:
synctex_parser_readme.txt
synctex_parser_version.txt
synctex_parser_utils.c
synctex_parser_utils.h
synctex_parser_local.h
synctex_parser.h
synctex_parser.c
The file you are reading contains more informations about the SyncTeX parser history.
In order to support SyncTeX in a viewer, it is sufficient to include
in the source the files synctex_parser.h and synctex_parser.c.
The synctex parser usage is described in synctex_parser.h header file.
The other files are used by tex engines or by the synctex command line utility:
ChangeLog
README.txt
am
man1
man5
synctex-common.h
synctex-convert.sh
synctex-e-mem.ch0
synctex-e-mem.ch1
synctex-e-rec.ch0
synctex-e-rec.ch1
synctex-etex.h
synctex-mem.ch0
synctex-mem.ch1
synctex-mem.ch2
synctex-pdf-rec.ch2
synctex-pdftex.h
synctex-rec.ch0
synctex-rec.ch1
synctex-rec.ch2
synctex-tex.h
synctex-xe-mem.ch2
synctex-xe-rec.ch2
synctex-xe-rec.ch3
synctex-xetex.h
synctex.c
synctex.defines
synctex.h
synctex_main.c
tests
Version:
--------
This is version 1, which refers to the synctex output file format.
The files are identified by a build number.
In order to help developers to automatically manage the version and build numbers
and download the parser only when necessary, the synctex_parser.version
is an ASCII text file just containing the current version and build numbers.
History:
--------
1.1: Thu Jul 17 09:28:13 UTC 2008
- First official version available in TeXLive 2008 DVD.
Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below.
1.2: Tue Sep 2 10:28:32 UTC 2008
- Correction for ConTeXt support in the edit query.
The previous method was assuming that TeX boxes do not overlap,
which is reasonable for LaTeX but not for ConTeXt.
This assumption is no longer considered.
1.3: Fri Sep 5 09:39:57 UTC 2008
- Local variable "read" renamed to "already_read" to avoid conflicts.
- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance
- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman)
- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization
1.4: Fri Sep 12 08:12:34 UTC 2008
- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747).
As a consequence, a crash was observed.
- Some typos are fixed.
1.6: Mon Nov 3 20:20:02 UTC 2008
- The bug that prevented synchronization with compressed files on windows has been fixed.
- New interface to allow system specific customization.
- Note that some APIs have changed.
1.8: Mer 8 jul 2009 11:32:38 UTC
Note that version 1.7 was delivered privately.
- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation
- bug fix: the synctex command line tool was broken when updating a .synctex file
- enhancement: better accuracy of the synchronization process
- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory.
The new -d option of the synctex command line tool manages this situation.
This is handy when using something like tex -output-directory=DIR ...
1.9: Wed Nov 4 11:52:35 UTC 2009
- Various typo fixed
- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing
- New conditional created because OutputDebugStringA is only available since Windows 2K professional
1.10: Sun Jan 10 10:12:32 UTC 2010
- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment.
Concerns the synctex tool.
1.11: Sun Jan 17 09:12:31 UTC 2010
- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'.
Only 3rd party tools are concerned.
1.12: Mon Jul 19 21:52:10 UTC 2010
- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return,
causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince.
1.13: Fri Mar 11 07:39:12 UTC 2011
- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388).
- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior).
Only 3rd party tools are concerned.
1.14: Fri Apr 15 19:10:57 UTC 2011
- taking output_directory into account
- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file.
- Merging with LuaTeX's version of synctex.c
1.15: Fri Jun 10 14:10:17 UTC 2011
This concerns the synctex command line tool and 3rd party developers.
TeX and friends are not concerned by these changes.
- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned
- Support for LuaTeX convention of './' file prefixing
1.16: Tue Jun 14 08:23:30 UTC 2011
This concerns the synctex command line tool and 3rd party developers.
TeX and friends are not concerned by these changes.
- Better forward search (thanks Jose Alliste)
- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows
Acknowledgments:
----------------
The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
and significant help from XeTeX developer Jonathan Kew
Nota Bene:
----------
If you include or use a significant part of the synctex package into a software,
I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
Copyright (c) 2008-2011 jerome DOT laurens AT u-bourgogne DOT fr

View file

@ -1,479 +0,0 @@
/*
Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the SyncTeX package.
Latest Revision: Tue Jun 14 08:23:30 UTC 2011
Version: 1.16
See synctex_parser_readme.txt for more details
License:
--------
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
*/
/* In this file, we find all the functions that may depend on the operating system. */
#include <synctex_parser_utils.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__)
#define SYNCTEX_WINDOWS 1
#endif
#ifdef _WIN32_WINNT_WINXP
#define SYNCTEX_RECENT_WINDOWS 1
#endif
#ifdef SYNCTEX_WINDOWS
#include <windows.h>
#endif
void *_synctex_malloc(size_t size) {
void * ptr = malloc(size);
if(ptr) {
/* There used to be a switch to use bzero because it is more secure. JL */
memset(ptr,0, size);
}
return (void *)ptr;
}
int _synctex_error(const char * reason,...) {
va_list arg;
int result;
va_start (arg, reason);
# ifdef SYNCTEX_RECENT_WINDOWS
{/* This code is contributed by William Blum.
As it does not work on some older computers,
the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one.
According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx
Minimum supported client Windows 2000 Professional
Minimum supported server Windows 2000 Server
People running Windows 2K standard edition will not have OutputDebugStringA.
JL.*/
char *buff;
size_t len;
OutputDebugStringA("SyncTeX ERROR: ");
len = _vscprintf(reason, arg) + 1;
buff = (char*)malloc( len * sizeof(char) );
result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: ");
OutputDebugStringA(buff);
OutputDebugStringA("\n");
free(buff);
}
# else
result = fprintf(stderr,"SyncTeX ERROR: ");
result += vfprintf(stderr, reason, arg);
result += fprintf(stderr,"\n");
# endif
va_end (arg);
return result;
}
/* strip the last extension of the given string, this string is modified! */
void _synctex_strip_last_path_extension(char * string) {
if(NULL != string){
char * last_component = NULL;
char * last_extension = NULL;
char * next = NULL;
/* first we find the last path component */
if(NULL == (last_component = strstr(string,"/"))){
last_component = string;
} else {
++last_component;
while((next = strstr(last_component,"/"))){
last_component = next+1;
}
}
# ifdef SYNCTEX_WINDOWS
/* On Windows, the '\' is also a path separator. */
while((next = strstr(last_component,"\\"))){
last_component = next+1;
}
# endif
/* then we find the last path extension */
if((last_extension = strstr(last_component,"."))){
++last_extension;
while((next = strstr(last_extension,"."))){
last_extension = next+1;
}
--last_extension;/* back to the "." */
if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/
last_extension[0] = '\0';
}
}
}
}
const char * synctex_ignore_leading_dot_slash(const char * name)
{
while(SYNCTEX_IS_DOT(*name) && SYNCTEX_IS_PATH_SEPARATOR(name[1])) {
name += 2;
while (SYNCTEX_IS_PATH_SEPARATOR(*name)) {
++name;
}
}
return name;
}
/* Compare two file names, windows is sometimes case insensitive... */
synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) {
/* Remove the leading regex '(\./+)*' in both rhs and lhs */
lhs = synctex_ignore_leading_dot_slash(lhs);
rhs = synctex_ignore_leading_dot_slash(rhs);
# if SYNCTEX_WINDOWS
/* On Windows, filename should be compared case insensitive.
* The characters '/' and '\' are both valid path separators.
* There will be a very serious problem concerning UTF8 because
* not all the characters must be toupper...
* I would like to have URL's instead of filenames. */
next_character:
if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */
if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */
return synctex_NO;
}
} else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */
return synctex_NO;
} else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */
return synctex_NO;
} else if (!*lhs) {/* lhs is at the end of the string */
return *rhs ? synctex_NO : synctex_YES;
} else if(!*rhs) {/* rhs is at the end of the string but not lhs */
return synctex_NO;
}
++lhs;
++rhs;
goto next_character;
# else
return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO;
# endif
}
synctex_bool_t _synctex_path_is_absolute(const char * name) {
if(!strlen(name)) {
return synctex_NO;
}
# if SYNCTEX_WINDOWS
if(strlen(name)>2) {
return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO;
}
return synctex_NO;
# else
return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO;
# endif
}
/* We do not take care of UTF-8 */
const char * _synctex_last_path_component(const char * name) {
const char * c = name+strlen(name);
if(c>name) {
if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) {
do {
--c;
if(SYNCTEX_IS_PATH_SEPARATOR(*c)) {
return c+1;
}
} while(c>name);
}
return c;/* the last path component is the void string*/
}
return c;
}
int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) {
const char * lpc;
if(src && dest_ref) {
# define dest (*dest_ref)
dest = NULL; /* Default behavior: no change and sucess. */
lpc = _synctex_last_path_component(src);
if(strlen(lpc)) {
if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') {
/* We are in the situation where adding the quotes is allowed. */
/* Time to add the quotes. */
/* Consistency test: we must have dest+size>dest+strlen(dest)+2
* or equivalently: strlen(dest)+2<size (see below) */
if(strlen(src)<size) {
if((dest = (char *)malloc(size+2))) {
char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */
if(dest != strncpy(dest,src,size)) {
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem");
free(dest);
dest = NULL;/* Don't forget to reinitialize. */
return -2;
}
memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */
dpc[0]='"';
dpc[strlen(dpc)+1]='\0';/* Consistency test */
dpc[strlen(dpc)]='"';
return 0; /* Success. */
}
return -1; /* Memory allocation error. */
}
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency");
return -3;
}
return 0; /* Success. */
}
return 0; /* No last path component. */
# undef dest
}
return 1; /* Bad parameter, this value is subject to changes. */
}
/* The client is responsible of the management of the returned string, if any. */
char * _synctex_merge_strings(const char * first,...);
char * _synctex_merge_strings(const char * first,...) {
va_list arg;
size_t size = 0;
const char * temp;
/* First retrieve the size necessary to store the merged string */
va_start (arg, first);
temp = first;
do {
size_t len = strlen(temp);
if(UINT_MAX-len<size) {
_synctex_error("! _synctex_merge_strings: Capacity exceeded.");
return NULL;
}
size+=len;
} while( (temp = va_arg(arg, const char *)) != NULL);
va_end(arg);
if(size>0) {
char * result = NULL;
++size;
/* Create the memory storage */
if(NULL!=(result = (char *)malloc(size))) {
char * dest = result;
va_start (arg, first);
temp = first;
do {
if((size = strlen(temp))>0) {
/* There is something to merge */
if(dest != strncpy(dest,temp,size)) {
_synctex_error("! _synctex_merge_strings: Copy problem");
free(result);
result = NULL;
return NULL;
}
dest += size;
}
} while( (temp = va_arg(arg, const char *)) != NULL);
va_end(arg);
dest[0]='\0';/* Terminate the merged string */
return result;
}
_synctex_error("! _synctex_merge_strings: Memory problem");
return NULL;
}
return NULL;
}
/* The purpose of _synctex_get_name is to find the name of the synctex file.
* There is a list of possible filenames from which we return the most recent one and try to remove all the others.
* With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate.
*/
int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref)
{
if(output && synctex_name_ref && io_mode_ref) {
/* If output is already absolute, we just have to manage the quotes and the compress mode */
size_t size = 0;
char * synctex_name = NULL;
synctex_io_mode_t io_mode = *io_mode_ref;
const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/
/* Do we have a real base name ? */
if(strlen(base_name)>0) {
/* Yes, we do. */
const char * temp = NULL;
char * core_name = NULL; /* base name of output without path extension. */
char * dir_name = NULL; /* dir name of output */
char * quoted_core_name = NULL;
char * basic_name = NULL;
char * gz_name = NULL;
char * quoted_name = NULL;
char * quoted_gz_name = NULL;
char * build_name = NULL;
char * build_gz_name = NULL;
char * build_quoted_name = NULL;
char * build_quoted_gz_name = NULL;
struct stat buf;
time_t the_time = 0;
/* Create core_name: let temp point to the dot before the path extension of base_name;
* We start form the \0 terminating character and scan the string upward until we find a dot.
* The leading dot is not accepted. */
if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) {
/* There is a dot and it is not at the leading position */
if(NULL == (core_name = (char *)malloc(size+1))) {
_synctex_error("! _synctex_get_name: Memory problem 1");
return -1;
}
if(core_name != strncpy(core_name,base_name,size)) {
_synctex_error("! _synctex_get_name: Copy problem 1");
free(core_name);
dir_name = NULL;
return -2;
}
core_name[size] = '\0';
} else {
/* There is no path extension,
* Just make a copy of base_name */
core_name = _synctex_merge_strings(base_name);
}
/* core_name is properly set up, owned by "self". */
/* creating dir_name. */
size = strlen(output)-strlen(base_name);
if(size>0) {
/* output contains more than one path component */
if(NULL == (dir_name = (char *)malloc(size+1))) {
_synctex_error("! _synctex_get_name: Memory problem");
free(core_name);
dir_name = NULL;
return -1;
}
if(dir_name != strncpy(dir_name,output,size)) {
_synctex_error("! _synctex_get_name: Copy problem");
free(dir_name);
dir_name = NULL;
free(core_name);
dir_name = NULL;
return -2;
}
dir_name[size] = '\0';
}
/* dir_name is properly set up. It ends with a path separator, if non void. */
/* creating quoted_core_name. */
if(strchr(core_name,' ')) {
quoted_core_name = _synctex_merge_strings("\"",core_name,"\"");
}
/* quoted_core_name is properly set up. */
if(dir_name &&strlen(dir_name)>0) {
basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL);
}
} else {
basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL);
}
}
if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) {
temp = build_directory + size - 1;
if(_synctex_path_is_absolute(temp)) {
build_name = _synctex_merge_strings(build_directory,basic_name,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL);
}
} else {
build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL);
if(quoted_core_name && strlen(quoted_core_name)>0) {
build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL);
}
}
}
if(basic_name) {
gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL);
}
if(quoted_name) {
quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL);
}
if(build_name) {
build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL);
}
if(build_quoted_name) {
build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL);
}
/* All the others names are properly set up... */
/* retain the most recently modified file */
# define TEST(FILENAME,COMPRESS_MODE) \
if(FILENAME) {\
if (stat(FILENAME, &buf)) { \
free(FILENAME);\
FILENAME = NULL;\
} else if (buf.st_mtime>the_time) { \
the_time=buf.st_mtime; \
synctex_name = FILENAME; \
if (COMPRESS_MODE) { \
io_mode |= synctex_io_gz_mask; \
} else { \
io_mode &= ~synctex_io_gz_mask; \
} \
} \
}
TEST(basic_name,synctex_DONT_COMPRESS);
TEST(gz_name,synctex_COMPRESS);
TEST(quoted_name,synctex_DONT_COMPRESS);
TEST(quoted_gz_name,synctex_COMPRESS);
TEST(build_name,synctex_DONT_COMPRESS);
TEST(build_gz_name,synctex_COMPRESS);
TEST(build_quoted_name,synctex_DONT_COMPRESS);
TEST(build_quoted_gz_name,synctex_COMPRESS);
# undef TEST
/* Free all the intermediate filenames, except the one that will be used as returned value. */
# define CLEAN_AND_REMOVE(FILENAME) \
if(FILENAME && (FILENAME!=synctex_name)) {\
remove(FILENAME);\
printf("synctex tool info: %s removed\n",FILENAME);\
free(FILENAME);\
FILENAME = NULL;\
}
CLEAN_AND_REMOVE(basic_name);
CLEAN_AND_REMOVE(gz_name);
CLEAN_AND_REMOVE(quoted_name);
CLEAN_AND_REMOVE(quoted_gz_name);
CLEAN_AND_REMOVE(build_name);
CLEAN_AND_REMOVE(build_gz_name);
CLEAN_AND_REMOVE(build_quoted_name);
CLEAN_AND_REMOVE(build_quoted_gz_name);
# undef CLEAN_AND_REMOVE
/* set up the returned values */
* synctex_name_ref = synctex_name;
* io_mode_ref = io_mode;
return 0;
}
return -1;/* bad argument */
}
return -2;
}
const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) {
static const char * synctex_io_modes[4] = {"r","rb","a","ab"};
unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste
return synctex_io_modes[index];
}

View file

@ -1,141 +0,0 @@
/*
Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr
This file is part of the SyncTeX package.
Latest Revision: Tue Jun 14 08:23:30 UTC 2011
Version: 1.16
See synctex_parser_readme.txt for more details
License:
--------
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE
Except as contained in this notice, the name of the copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization from the copyright holder.
*/
/* The utilities declared here are subject to conditional implementation.
* All the operating system special stuff goes here.
* The problem mainly comes from file name management: path separator, encoding...
*/
# define synctex_bool_t int
# define synctex_YES -1
# define synctex_ADD_QUOTES -1
# define synctex_COMPRESS -1
# define synctex_NO 0
# define synctex_DONT_ADD_QUOTES 0
# define synctex_DONT_COMPRESS 0
#ifndef __SYNCTEX_PARSER_UTILS__
# define __SYNCTEX_PARSER_UTILS__
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
# if _WIN32
# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c)
# else
# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c)
# endif
# if _WIN32
# define SYNCTEX_IS_DOT(c) ('.' == c)
# else
# define SYNCTEX_IS_DOT(c) ('.' == c)
# endif
/* This custom malloc functions initializes to 0 the newly allocated memory.
* There is no bzero function on windows. */
void *_synctex_malloc(size_t size);
/* This is used to log some informational message to the standard error stream.
* On Windows, the stderr stream is not exposed and another method is used.
* The return value is the number of characters printed. */
int _synctex_error(const char * reason,...);
/* strip the last extension of the given string, this string is modified!
* This function depends on the OS because the path separator may differ.
* This should be discussed more precisely. */
void _synctex_strip_last_path_extension(char * string);
/* Compare two file names, windows is sometimes case insensitive...
* The given strings may differ stricto sensu, but represent the same file name.
* It might not be the real way of doing things.
* The return value is an undefined non 0 value when the two file names are equivalent.
* It is 0 otherwise. */
synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs);
/* Description forthcoming.*/
synctex_bool_t _synctex_path_is_absolute(const char * name);
/* Description forthcoming...*/
const char * _synctex_last_path_component(const char * name);
/* If the core of the last path component of src is not already enclosed with double quotes ('"')
* and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added.
* In all other cases, no destination buffer is created and the src is not copied.
* 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter.
* This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces
* were not managed in a standard way.
* On success, the caller owns the buffer pointed to by dest_ref (is any) and
* is responsible of freeing the memory when done.
* The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/
int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size);
/* These are the possible extensions of the synctex file */
extern const char * synctex_suffix;
extern const char * synctex_suffix_gz;
typedef unsigned int synctex_io_mode_t;
typedef enum {
synctex_io_append_mask = 1,
synctex_io_gz_mask = synctex_io_append_mask<<1
} synctex_io_mode_masks_t;
typedef enum {
synctex_compress_mode_none = 0,
synctex_compress_mode_gz = 1
} synctex_compress_mode_t;
int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref);
/* returns the correct mode required by fopen and gzopen from the given io_mode */
const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode);
const char * synctex_ignore_leading_dot_slash(const char * name);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -101,7 +101,13 @@ Hello world
expect(error).to.not.exist expect(error).to.not.exist
expect(result).to.deep.equal({ expect(result).to.deep.equal({
pdf: [ pdf: [
{ page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, {
page: 1,
h: 133.768356,
v: 134.764618,
height: 6.918498,
width: 343.71106,
},
], ],
}) })
done() done()

View file

@ -59,7 +59,13 @@ Hello world
} }
expect(pdfPositions).to.deep.equal({ expect(pdfPositions).to.deep.equal({
pdf: [ pdf: [
{ page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, {
page: 1,
h: 133.768356,
v: 134.764618,
height: 6.918498,
width: 343.71106,
},
], ],
}) })
return done() return done()

View file

@ -1,13 +1,16 @@
const chai = require('chai') const chai = require('chai')
const sinonChai = require('sinon-chai')
const SandboxedModule = require('sandboxed-module') const SandboxedModule = require('sandboxed-module')
// Setup should interface // Setup chai
chai.should() chai.should()
chai.use(sinonChai)
// Global SandboxedModule settings // Global SandboxedModule settings
SandboxedModule.configure({ SandboxedModule.configure({
requires: { requires: {
'logger-sharelatex': { 'logger-sharelatex': {
debug() {},
log() {}, log() {},
info() {}, info() {},
warn() {}, warn() {},

View file

@ -34,6 +34,12 @@ describe('CompileManager', function () {
build: 1234, build: 1234,
}, },
] ]
this.commandOutput = 'Dummy output'
this.compileBaseDir = '/compile/dir'
this.outputBaseDir = '/output/dir'
this.compileDir = `${this.compileBaseDir}/${this.projectId}-${this.userId}`
this.outputDir = `${this.outputBaseDir}/${this.projectId}-${this.userId}`
this.proc = new EventEmitter() this.proc = new EventEmitter()
this.proc.stdout = new EventEmitter() this.proc.stdout = new EventEmitter()
this.proc.stderr = new EventEmitter() this.proc.stderr = new EventEmitter()
@ -53,11 +59,9 @@ describe('CompileManager', function () {
} }
this.Settings = { this.Settings = {
path: { path: {
compilesDir: '/compiles/dir', compilesDir: this.compileBaseDir,
outputDir: '/output/dir', outputDir: this.outputBaseDir,
}, synctexBaseDir: sinon.stub(),
synctexBaseDir() {
return '/compile'
}, },
clsi: { clsi: {
docker: { docker: {
@ -65,12 +69,15 @@ describe('CompileManager', function () {
}, },
}, },
} }
this.Settings.path.synctexBaseDir
.withArgs(`${this.projectId}-${this.userId}`)
.returns(this.compileDir)
this.child_process = { this.child_process = {
exec: sinon.stub(), exec: sinon.stub(),
spawn: sinon.stub().returns(this.proc), spawn: sinon.stub().returns(this.proc),
} }
this.CommandRunner = { this.CommandRunner = {
run: sinon.stub().yields(), run: sinon.stub().yields(null, { stdout: this.commandOutput }),
} }
this.DraftModeManager = { this.DraftModeManager = {
injectDraftMode: sinon.stub().yields(), injectDraftMode: sinon.stub().yields(),
@ -83,6 +90,11 @@ describe('CompileManager', function () {
runner((err, ...result) => callback(err, ...result)) runner((err, ...result) => callback(err, ...result))
}), }),
} }
this.SynctexOutputParser = {
parseViewOutput: sinon.stub(),
parseEditOutput: sinon.stub(),
}
this.fs = { this.fs = {
lstat: sinon.stub(), lstat: sinon.stub(),
stat: sinon.stub(), stat: sinon.stub(),
@ -104,6 +116,7 @@ describe('CompileManager', function () {
'./DraftModeManager': this.DraftModeManager, './DraftModeManager': this.DraftModeManager,
'./TikzManager': this.TikzManager, './TikzManager': this.TikzManager,
'./LockManager': this.LockManager, './LockManager': this.LockManager,
'./SynctexOutputParser': this.SynctexOutputParser,
fs: this.fs, fs: this.fs,
'fs-extra': this.fse, 'fs-extra': this.fse,
}, },
@ -124,9 +137,6 @@ describe('CompileManager', function () {
compileGroup: (this.compileGroup = 'compile-group'), compileGroup: (this.compileGroup = 'compile-group'),
} }
this.env = {} this.env = {}
this.Settings.compileDir = 'compiles'
this.compileDir = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`
this.outputDir = `${this.Settings.path.outputDir}/${this.projectId}-${this.userId}`
}) })
describe('when the project is locked', function () { describe('when the project is locked', function () {
@ -278,13 +288,7 @@ describe('CompileManager', function () {
it('should remove the project directory', function () { it('should remove the project directory', function () {
this.child_process.spawn this.child_process.spawn
.calledWith('rm', [ .calledWith('rm', ['-r', '-f', '--', this.compileDir, this.outputDir])
'-r',
'-f',
'--',
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
`${this.Settings.path.outputDir}/${this.projectId}-${this.userId}`,
])
.should.equal(true) .should.equal(true)
}) })
@ -312,13 +316,7 @@ describe('CompileManager', function () {
it('should remove the project directory', function () { it('should remove the project directory', function () {
this.child_process.spawn this.child_process.spawn
.calledWith('rm', [ .calledWith('rm', ['-r', '-f', '--', this.compileDir, this.outputDir])
'-r',
'-f',
'--',
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
`${this.Settings.path.outputDir}/${this.projectId}-${this.userId}`,
])
.should.equal(true) .should.equal(true)
}) })
@ -326,7 +324,7 @@ describe('CompileManager', function () {
this.callback.calledWithExactly(sinon.match(Error)).should.equal(true) this.callback.calledWithExactly(sinon.match(Error)).should.equal(true)
this.callback.args[0][0].message.should.equal( this.callback.args[0][0].message.should.equal(
`rm -r ${this.Settings.path.compilesDir}/${this.projectId}-${this.userId} ${this.Settings.path.outputDir}/${this.projectId}-${this.userId} failed: ${this.error}` `rm -r ${this.compileDir} ${this.outputDir} failed: ${this.error}`
) )
}) })
}) })
@ -341,9 +339,7 @@ describe('CompileManager', function () {
this.height = 234.56 this.height = 234.56
this.line = 5 this.line = 5
this.column = 3 this.column = 3
this.file_name = 'main.tex' this.filename = 'main.tex'
this.Settings.path.synctexBaseDir = projectId =>
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`
}) })
describe('syncFromCode', function () { describe('syncFromCode', function () {
@ -353,12 +349,14 @@ describe('CompileManager', function () {
return true return true
}, },
}) })
this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` this.records = [{ page: 1, h: 2, v: 3, width: 4, height: 5 }]
this.CommandRunner.run.yields(null, { stdout: this.stdout }) this.SynctexOutputParser.parseViewOutput
.withArgs(this.commandOutput)
.returns(this.records)
this.CompileManager.syncFromCode( this.CompileManager.syncFromCode(
this.projectId, this.projectId,
this.userId, this.userId,
this.file_name, this.filename,
this.line, this.line,
this.column, this.column,
'', '',
@ -367,39 +365,30 @@ describe('CompileManager', function () {
}) })
it('should execute the synctex binary', function () { it('should execute the synctex binary', function () {
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf` const outputFilePath = `${this.compileDir}/output.pdf`
const filePath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/${this.file_name}` const inputFilePath = `${this.compileDir}/${this.filename}`
this.CommandRunner.run this.CommandRunner.run.should.have.been.calledWith(
.calledWith(
`${this.projectId}-${this.userId}`, `${this.projectId}-${this.userId}`,
[ [
'/opt/synctex', 'synctex',
'code', 'view',
synctexPath, '-i',
filePath, `${this.line}:${this.column}:${inputFilePath}`,
this.line, '-o',
this.column, outputFilePath,
], ],
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`, this.compileDir,
this.Settings.clsi.docker.image, this.Settings.clsi.docker.image,
60000, 60000,
{} {}
) )
.should.equal(true)
}) })
it('should call the callback with the parsed output', function () { it('should call the callback with the parsed output', function () {
this.callback this.callback.should.have.been.calledWith(
.calledWith(null, [ null,
{ sinon.match.array.deepEquals(this.records)
page: this.page, )
h: this.h,
v: this.v,
height: this.height,
width: this.width,
},
])
.should.equal(true)
}) })
describe('with a custom imageName', function () { describe('with a custom imageName', function () {
@ -409,7 +398,7 @@ describe('CompileManager', function () {
this.CompileManager.syncFromCode( this.CompileManager.syncFromCode(
this.projectId, this.projectId,
this.userId, this.userId,
this.file_name, this.filename,
this.line, this.line,
this.column, this.column,
customImageName, customImageName,
@ -418,25 +407,23 @@ describe('CompileManager', function () {
}) })
it('should execute the synctex binary in a custom docker image', function () { it('should execute the synctex binary in a custom docker image', function () {
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf` const outputFilePath = `${this.compileDir}/output.pdf`
const filePath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/${this.file_name}` const inputFilePath = `${this.compileDir}/${this.filename}`
this.CommandRunner.run this.CommandRunner.run.should.have.been.calledWith(
.calledWith(
`${this.projectId}-${this.userId}`, `${this.projectId}-${this.userId}`,
[ [
'/opt/synctex', 'synctex',
'code', 'view',
synctexPath, '-i',
filePath, `${this.line}:${this.column}:${inputFilePath}`,
this.line, '-o',
this.column, outputFilePath,
], ],
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`, this.compileDir,
customImageName, customImageName,
60000, 60000,
{} {}
) )
.should.equal(true)
}) })
}) })
}) })
@ -448,8 +435,10 @@ describe('CompileManager', function () {
return true return true
}, },
}) })
this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/${this.file_name}\t${this.line}\t${this.column}\n` this.records = [{ file: 'main.tex', line: 1, column: 1 }]
this.CommandRunner.run.yields(null, { stdout: this.stdout }) this.SynctexOutputParser.parseEditOutput
.withArgs(this.commandOutput, this.compileDir)
.returns(this.records)
this.CompileManager.syncFromPdf( this.CompileManager.syncFromPdf(
this.projectId, this.projectId,
this.userId, this.userId,
@ -462,29 +451,27 @@ describe('CompileManager', function () {
}) })
it('should execute the synctex binary', function () { it('should execute the synctex binary', function () {
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf` const outputFilePath = `${this.compileDir}/output.pdf`
this.CommandRunner.run this.CommandRunner.run.should.have.been.calledWith(
.calledWith(
`${this.projectId}-${this.userId}`, `${this.projectId}-${this.userId}`,
['/opt/synctex', 'pdf', synctexPath, this.page, this.h, this.v], [
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`, 'synctex',
'edit',
'-o',
`${this.page}:${this.h}:${this.v}:${outputFilePath}`,
],
this.compileDir,
this.Settings.clsi.docker.image, this.Settings.clsi.docker.image,
60000, 60000,
{} {}
) )
.should.equal(true)
}) })
it('should call the callback with the parsed output', function () { it('should call the callback with the parsed output', function () {
this.callback this.callback.should.have.been.calledWith(
.calledWith(null, [ null,
{ sinon.match.array.deepEquals(this.records)
file: this.file_name, )
line: this.line,
column: this.column,
},
])
.should.equal(true)
}) })
describe('with a custom imageName', function () { describe('with a custom imageName', function () {
@ -503,12 +490,17 @@ describe('CompileManager', function () {
}) })
it('should execute the synctex binary in a custom docker image', function () { it('should execute the synctex binary in a custom docker image', function () {
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf` const outputFilePath = `${this.compileDir}/output.pdf`
this.CommandRunner.run this.CommandRunner.run
.calledWith( .calledWith(
`${this.projectId}-${this.userId}`, `${this.projectId}-${this.userId}`,
['/opt/synctex', 'pdf', synctexPath, this.page, this.h, this.v], [
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`, 'synctex',
'edit',
'-o',
`${this.page}:${this.h}:${this.v}:${outputFilePath}`,
],
this.compileDir,
customImageName, customImageName,
60000, 60000,
{} {}
@ -525,22 +517,20 @@ describe('CompileManager', function () {
this.fs.readFile.yields(null, this.stdout) this.fs.readFile.yields(null, this.stdout)
this.timeout = 60 * 1000 this.timeout = 60 * 1000
this.file_name = 'main.tex' this.filename = 'main.tex'
this.Settings.path.compilesDir = '/local/compile/directory'
this.image = 'example.com/image' this.image = 'example.com/image'
this.CompileManager.wordcount( this.CompileManager.wordcount(
this.projectId, this.projectId,
this.userId, this.userId,
this.file_name, this.filename,
this.image, this.image,
this.callback this.callback
) )
}) })
it('should run the texcount command', function () { it('should run the texcount command', function () {
this.directory = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}` this.filePath = `$COMPILE_DIR/${this.filename}`
this.filePath = `$COMPILE_DIR/${this.file_name}`
this.command = [ this.command = [
'texcount', 'texcount',
'-nocol', '-nocol',
@ -553,7 +543,7 @@ describe('CompileManager', function () {
.calledWith( .calledWith(
`${this.projectId}-${this.userId}`, `${this.projectId}-${this.userId}`,
this.command, this.command,
this.directory, this.compileDir,
this.image, this.image,
this.timeout, this.timeout,
{} {}

View file

@ -0,0 +1,116 @@
const Path = require('path')
const SandboxedModule = require('sandboxed-module')
const { expect } = require('chai')
const MODULE_PATH = Path.join(__dirname, '../../../app/js/SynctexOutputParser')
describe('SynctexOutputParser', function () {
beforeEach(function () {
this.SynctexOutputParser = SandboxedModule.require(MODULE_PATH)
})
describe('parseViewOutput', function () {
it('parses valid output', function () {
const output = `This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:/compile/output.pdf
Page:1
x:136.537964
y:661.437561
h:133.768356
v:663.928223
W:343.711060
H:9.962640
before:
offset:-1
middle:
after:
Output:/compile/output.pdf
Page:2
x:178.769592
y:649.482361
h:134.768356
v:651.973022
W:342.711060
H:19.962640
before:
offset:-1
middle:
after:
SyncTeX result end
`
const records = this.SynctexOutputParser.parseViewOutput(output)
expect(records).to.deep.equal([
{
page: 1,
h: 133.768356,
v: 663.928223,
width: 343.71106,
height: 9.96264,
},
{
page: 2,
h: 134.768356,
v: 651.973022,
width: 342.71106,
height: 19.96264,
},
])
})
it('handles garbage', function () {
const output = 'This computer is on strike!'
const records = this.SynctexOutputParser.parseViewOutput(output)
expect(records).to.deep.equal([])
})
})
describe('parseEditOutput', function () {
it('parses valid output', function () {
const output = `This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:/compile/output.pdf
Input:/compile/main.tex
Line:17
Column:-1
Offset:0
Context:
SyncTeX result end
`
const records = this.SynctexOutputParser.parseEditOutput(
output,
'/compile'
)
expect(records).to.deep.equal([
{ file: 'main.tex', line: 17, column: -1 },
])
})
it('handles values that contain colons', function () {
const output = `This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:/compile/output.pdf
Input:/compile/this-file:has-a-weird-name.tex
Line:17
Column:-1
Offset:0
Context:
SyncTeX result end
`
const records = this.SynctexOutputParser.parseEditOutput(
output,
'/compile'
)
expect(records).to.deep.equal([
{ file: 'this-file:has-a-weird-name.tex', line: 17, column: -1 },
])
})
it('handles garbage', function () {
const output = '2 + 2 = 4'
const records = this.SynctexOutputParser.parseEditOutput(output)
expect(records).to.deep.equal([])
})
})
})