mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #4893 from overleaf/em-synctex
Use the synctex distributed with TeX Live GitOrigin-RevId: 5a133f21f48fd1e217ab463e8cb2a5cdec8be1af
This commit is contained in:
parent
fa2567aa61
commit
9ee92daea3
27 changed files with 380 additions and 5667 deletions
|
@ -81,7 +81,6 @@ services:
|
|||
|
||||
# SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true'
|
||||
# SANDBOXED_COMPILES_HOST_DIR: '/var/sharelatex_data/data/compiles'
|
||||
# SYNCTEX_BIN_HOST_PATH: '/var/sharelatex_data/bin/synctex'
|
||||
|
||||
# DOCKER_RUNNER: 'false'
|
||||
|
||||
|
|
|
@ -26,11 +26,6 @@ RUN node genScript install | 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
|
||||
# --------------------------------------------------
|
||||
ADD server-ce/runit /etc/service
|
||||
|
|
|
@ -61,6 +61,7 @@ RUN mkdir /install-tl-unx \
|
|||
&& tlmgr install --repository ${TEXLIVE_MIRROR} \
|
||||
latexmk \
|
||||
texcount \
|
||||
synctex \
|
||||
\
|
||||
&& rm -rf /install-tl-unx
|
||||
|
||||
|
|
|
@ -15,17 +15,4 @@ if [ -e '/var/run/docker.sock' ]; then
|
|||
usermod -aG dockeronhost www-data
|
||||
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
|
||||
|
|
|
@ -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/)
|
||||
* `SMOKE_TEST` - Whether to run smoke tests
|
||||
* `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`
|
||||
* `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`
|
||||
|
|
|
@ -17,6 +17,7 @@ const async = require('async')
|
|||
const Errors = require('./Errors')
|
||||
const CommandRunner = require('./CommandRunner')
|
||||
const { emitPdfStats } = require('./ContentCacheMetrics')
|
||||
const SynctexOutputParser = require('./SynctexOutputParser')
|
||||
|
||||
function getCompileName(projectId, userId) {
|
||||
if (userId != null) {
|
||||
|
@ -428,32 +429,44 @@ function syncFromCode(
|
|||
// wherever it is on the host.
|
||||
const compileName = getCompileName(projectId, userId)
|
||||
const baseDir = Settings.path.synctexBaseDir(compileName)
|
||||
const filePath = baseDir + '/' + filename
|
||||
const synctexPath = `${baseDir}/output.pdf`
|
||||
const command = ['code', synctexPath, filePath, line, column]
|
||||
const inputFilePath = Path.join(baseDir, filename)
|
||||
const outputFilePath = Path.join(baseDir, 'output.pdf')
|
||||
const command = [
|
||||
'synctex',
|
||||
'view',
|
||||
'-i',
|
||||
`${line}:${column}:${inputFilePath}`,
|
||||
'-o',
|
||||
outputFilePath,
|
||||
]
|
||||
_runSynctex(projectId, userId, command, imageName, (error, stdout) => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
logger.log(
|
||||
logger.debug(
|
||||
{ projectId, userId, filename, line, column, command, stdout },
|
||||
'synctex code output'
|
||||
)
|
||||
callback(null, _parseSynctexFromCodeOutput(stdout))
|
||||
callback(null, SynctexOutputParser.parseViewOutput(stdout))
|
||||
})
|
||||
}
|
||||
|
||||
function syncFromPdf(projectId, userId, page, h, v, imageName, callback) {
|
||||
const compileName = getCompileName(projectId, userId)
|
||||
const baseDir = Settings.path.synctexBaseDir(compileName)
|
||||
const synctexPath = `${baseDir}/output.pdf`
|
||||
const command = ['pdf', synctexPath, page, h, v]
|
||||
const outputFilePath = `${baseDir}/output.pdf`
|
||||
const command = [
|
||||
'synctex',
|
||||
'edit',
|
||||
'-o',
|
||||
`${page}:${h}:${v}:${outputFilePath}`,
|
||||
]
|
||||
_runSynctex(projectId, userId, command, imageName, (error, stdout) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
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) {
|
||||
command.unshift('/opt/synctex')
|
||||
|
||||
const directory = getCompileDir(projectId, userId)
|
||||
const timeout = 60 * 1000 // increased to allow for large projects
|
||||
const compileName = getCompileName(projectId, userId)
|
||||
const compileGroup = 'synctex'
|
||||
const defaultImageName =
|
||||
Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.image
|
||||
_checkFileExists(directory, 'output.synctex.gz', error => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
|
@ -496,10 +509,7 @@ function _runSynctex(projectId, userId, command, imageName, callback) {
|
|||
compileName,
|
||||
command,
|
||||
directory,
|
||||
imageName ||
|
||||
(Settings.clsi && Settings.clsi.docker
|
||||
? Settings.clsi.docker.image
|
||||
: undefined),
|
||||
imageName || defaultImageName,
|
||||
timeout,
|
||||
{},
|
||||
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) {
|
||||
logger.log({ projectId, userId, filename, image }, 'running wordcount')
|
||||
const filePath = `$COMPILE_DIR/${filename}`
|
||||
|
|
|
@ -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) {
|
||||
options.HostConfig.SecurityOpt.push(
|
||||
`seccomp=${Settings.clsi.docker.seccomp_profile}`
|
||||
|
|
113
services/clsi/app/js/SynctexOutputParser.js
Normal file
113
services/clsi/app/js/SynctexOutputParser.js
Normal 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.
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
echo "hello world"
|
||||
sleep 3
|
||||
echo "awake"
|
||||
/opt/synctex pdf /compile/output.pdf 1 100 200
|
|
@ -9,12 +9,10 @@ services:
|
|||
SHARELATEX_CONFIG: /app/config/settings.defaults.js
|
||||
DOCKER_RUNNER: "true"
|
||||
COMPILES_HOST_DIR: $PWD/compiles
|
||||
SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./compiles:/app/compiles
|
||||
- ./cache:/app/cache
|
||||
- ./bin/synctex:/app/bin/synctex
|
||||
|
||||
|
||||
ci:
|
||||
|
@ -25,10 +23,8 @@ services:
|
|||
SHARELATEX_CONFIG: /app/config/settings.defaults.js
|
||||
DOCKER_RUNNER: "true"
|
||||
COMPILES_HOST_DIR: $PWD/compiles
|
||||
SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex
|
||||
SQLITE_PATH: /app/compiles/db.sqlite
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:rw
|
||||
- ./compiles:/app/compiles
|
||||
- ./cache:/app/cache
|
||||
- ./bin/synctex:/app/bin/synctex
|
||||
|
|
|
@ -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/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 -- "$@"
|
||||
|
|
6
services/clsi/package-lock.json
generated
6
services/clsi/package-lock.json
generated
|
@ -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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"prettier": "^2.2.1",
|
||||
"sandboxed-module": "^2.0.3",
|
||||
"sinon": "~9.0.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"timekeeper": "2.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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];
|
||||
}
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
1.16
|
|
@ -101,7 +101,13 @@ Hello world
|
|||
expect(error).to.not.exist
|
||||
expect(result).to.deep.equal({
|
||||
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()
|
||||
|
|
|
@ -59,7 +59,13 @@ Hello world
|
|||
}
|
||||
expect(pdfPositions).to.deep.equal({
|
||||
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()
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
const chai = require('chai')
|
||||
const sinonChai = require('sinon-chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
// Setup should interface
|
||||
// Setup chai
|
||||
chai.should()
|
||||
chai.use(sinonChai)
|
||||
|
||||
// Global SandboxedModule settings
|
||||
SandboxedModule.configure({
|
||||
requires: {
|
||||
'logger-sharelatex': {
|
||||
debug() {},
|
||||
log() {},
|
||||
info() {},
|
||||
warn() {},
|
||||
|
|
|
@ -34,6 +34,12 @@ describe('CompileManager', function () {
|
|||
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.stdout = new EventEmitter()
|
||||
this.proc.stderr = new EventEmitter()
|
||||
|
@ -53,11 +59,9 @@ describe('CompileManager', function () {
|
|||
}
|
||||
this.Settings = {
|
||||
path: {
|
||||
compilesDir: '/compiles/dir',
|
||||
outputDir: '/output/dir',
|
||||
},
|
||||
synctexBaseDir() {
|
||||
return '/compile'
|
||||
compilesDir: this.compileBaseDir,
|
||||
outputDir: this.outputBaseDir,
|
||||
synctexBaseDir: sinon.stub(),
|
||||
},
|
||||
clsi: {
|
||||
docker: {
|
||||
|
@ -65,12 +69,15 @@ describe('CompileManager', function () {
|
|||
},
|
||||
},
|
||||
}
|
||||
this.Settings.path.synctexBaseDir
|
||||
.withArgs(`${this.projectId}-${this.userId}`)
|
||||
.returns(this.compileDir)
|
||||
this.child_process = {
|
||||
exec: sinon.stub(),
|
||||
spawn: sinon.stub().returns(this.proc),
|
||||
}
|
||||
this.CommandRunner = {
|
||||
run: sinon.stub().yields(),
|
||||
run: sinon.stub().yields(null, { stdout: this.commandOutput }),
|
||||
}
|
||||
this.DraftModeManager = {
|
||||
injectDraftMode: sinon.stub().yields(),
|
||||
|
@ -83,6 +90,11 @@ describe('CompileManager', function () {
|
|||
runner((err, ...result) => callback(err, ...result))
|
||||
}),
|
||||
}
|
||||
this.SynctexOutputParser = {
|
||||
parseViewOutput: sinon.stub(),
|
||||
parseEditOutput: sinon.stub(),
|
||||
}
|
||||
|
||||
this.fs = {
|
||||
lstat: sinon.stub(),
|
||||
stat: sinon.stub(),
|
||||
|
@ -104,6 +116,7 @@ describe('CompileManager', function () {
|
|||
'./DraftModeManager': this.DraftModeManager,
|
||||
'./TikzManager': this.TikzManager,
|
||||
'./LockManager': this.LockManager,
|
||||
'./SynctexOutputParser': this.SynctexOutputParser,
|
||||
fs: this.fs,
|
||||
'fs-extra': this.fse,
|
||||
},
|
||||
|
@ -124,9 +137,6 @@ describe('CompileManager', function () {
|
|||
compileGroup: (this.compileGroup = 'compile-group'),
|
||||
}
|
||||
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 () {
|
||||
|
@ -278,13 +288,7 @@ describe('CompileManager', function () {
|
|||
|
||||
it('should remove the project directory', function () {
|
||||
this.child_process.spawn
|
||||
.calledWith('rm', [
|
||||
'-r',
|
||||
'-f',
|
||||
'--',
|
||||
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
|
||||
`${this.Settings.path.outputDir}/${this.projectId}-${this.userId}`,
|
||||
])
|
||||
.calledWith('rm', ['-r', '-f', '--', this.compileDir, this.outputDir])
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
|
@ -312,13 +316,7 @@ describe('CompileManager', function () {
|
|||
|
||||
it('should remove the project directory', function () {
|
||||
this.child_process.spawn
|
||||
.calledWith('rm', [
|
||||
'-r',
|
||||
'-f',
|
||||
'--',
|
||||
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
|
||||
`${this.Settings.path.outputDir}/${this.projectId}-${this.userId}`,
|
||||
])
|
||||
.calledWith('rm', ['-r', '-f', '--', this.compileDir, this.outputDir])
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
|
@ -326,7 +324,7 @@ describe('CompileManager', function () {
|
|||
this.callback.calledWithExactly(sinon.match(Error)).should.equal(true)
|
||||
|
||||
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.line = 5
|
||||
this.column = 3
|
||||
this.file_name = 'main.tex'
|
||||
this.Settings.path.synctexBaseDir = projectId =>
|
||||
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`
|
||||
this.filename = 'main.tex'
|
||||
})
|
||||
|
||||
describe('syncFromCode', function () {
|
||||
|
@ -353,12 +349,14 @@ describe('CompileManager', function () {
|
|||
return true
|
||||
},
|
||||
})
|
||||
this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n`
|
||||
this.CommandRunner.run.yields(null, { stdout: this.stdout })
|
||||
this.records = [{ page: 1, h: 2, v: 3, width: 4, height: 5 }]
|
||||
this.SynctexOutputParser.parseViewOutput
|
||||
.withArgs(this.commandOutput)
|
||||
.returns(this.records)
|
||||
this.CompileManager.syncFromCode(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
this.file_name,
|
||||
this.filename,
|
||||
this.line,
|
||||
this.column,
|
||||
'',
|
||||
|
@ -367,39 +365,30 @@ describe('CompileManager', function () {
|
|||
})
|
||||
|
||||
it('should execute the synctex binary', function () {
|
||||
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf`
|
||||
const filePath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/${this.file_name}`
|
||||
this.CommandRunner.run
|
||||
.calledWith(
|
||||
const outputFilePath = `${this.compileDir}/output.pdf`
|
||||
const inputFilePath = `${this.compileDir}/${this.filename}`
|
||||
this.CommandRunner.run.should.have.been.calledWith(
|
||||
`${this.projectId}-${this.userId}`,
|
||||
[
|
||||
'/opt/synctex',
|
||||
'code',
|
||||
synctexPath,
|
||||
filePath,
|
||||
this.line,
|
||||
this.column,
|
||||
'synctex',
|
||||
'view',
|
||||
'-i',
|
||||
`${this.line}:${this.column}:${inputFilePath}`,
|
||||
'-o',
|
||||
outputFilePath,
|
||||
],
|
||||
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
|
||||
this.compileDir,
|
||||
this.Settings.clsi.docker.image,
|
||||
60000,
|
||||
{}
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with the parsed output', function () {
|
||||
this.callback
|
||||
.calledWith(null, [
|
||||
{
|
||||
page: this.page,
|
||||
h: this.h,
|
||||
v: this.v,
|
||||
height: this.height,
|
||||
width: this.width,
|
||||
},
|
||||
])
|
||||
.should.equal(true)
|
||||
this.callback.should.have.been.calledWith(
|
||||
null,
|
||||
sinon.match.array.deepEquals(this.records)
|
||||
)
|
||||
})
|
||||
|
||||
describe('with a custom imageName', function () {
|
||||
|
@ -409,7 +398,7 @@ describe('CompileManager', function () {
|
|||
this.CompileManager.syncFromCode(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
this.file_name,
|
||||
this.filename,
|
||||
this.line,
|
||||
this.column,
|
||||
customImageName,
|
||||
|
@ -418,25 +407,23 @@ describe('CompileManager', 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 filePath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/${this.file_name}`
|
||||
this.CommandRunner.run
|
||||
.calledWith(
|
||||
const outputFilePath = `${this.compileDir}/output.pdf`
|
||||
const inputFilePath = `${this.compileDir}/${this.filename}`
|
||||
this.CommandRunner.run.should.have.been.calledWith(
|
||||
`${this.projectId}-${this.userId}`,
|
||||
[
|
||||
'/opt/synctex',
|
||||
'code',
|
||||
synctexPath,
|
||||
filePath,
|
||||
this.line,
|
||||
this.column,
|
||||
'synctex',
|
||||
'view',
|
||||
'-i',
|
||||
`${this.line}:${this.column}:${inputFilePath}`,
|
||||
'-o',
|
||||
outputFilePath,
|
||||
],
|
||||
`${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`,
|
||||
this.compileDir,
|
||||
customImageName,
|
||||
60000,
|
||||
{}
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -448,8 +435,10 @@ describe('CompileManager', function () {
|
|||
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.CommandRunner.run.yields(null, { stdout: this.stdout })
|
||||
this.records = [{ file: 'main.tex', line: 1, column: 1 }]
|
||||
this.SynctexOutputParser.parseEditOutput
|
||||
.withArgs(this.commandOutput, this.compileDir)
|
||||
.returns(this.records)
|
||||
this.CompileManager.syncFromPdf(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
|
@ -462,29 +451,27 @@ describe('CompileManager', function () {
|
|||
})
|
||||
|
||||
it('should execute the synctex binary', function () {
|
||||
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf`
|
||||
this.CommandRunner.run
|
||||
.calledWith(
|
||||
const outputFilePath = `${this.compileDir}/output.pdf`
|
||||
this.CommandRunner.run.should.have.been.calledWith(
|
||||
`${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,
|
||||
60000,
|
||||
{}
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with the parsed output', function () {
|
||||
this.callback
|
||||
.calledWith(null, [
|
||||
{
|
||||
file: this.file_name,
|
||||
line: this.line,
|
||||
column: this.column,
|
||||
},
|
||||
])
|
||||
.should.equal(true)
|
||||
this.callback.should.have.been.calledWith(
|
||||
null,
|
||||
sinon.match.array.deepEquals(this.records)
|
||||
)
|
||||
})
|
||||
|
||||
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 () {
|
||||
const synctexPath = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}/output.pdf`
|
||||
const outputFilePath = `${this.compileDir}/output.pdf`
|
||||
this.CommandRunner.run
|
||||
.calledWith(
|
||||
`${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,
|
||||
60000,
|
||||
{}
|
||||
|
@ -525,22 +517,20 @@ describe('CompileManager', function () {
|
|||
this.fs.readFile.yields(null, this.stdout)
|
||||
|
||||
this.timeout = 60 * 1000
|
||||
this.file_name = 'main.tex'
|
||||
this.Settings.path.compilesDir = '/local/compile/directory'
|
||||
this.filename = 'main.tex'
|
||||
this.image = 'example.com/image'
|
||||
|
||||
this.CompileManager.wordcount(
|
||||
this.projectId,
|
||||
this.userId,
|
||||
this.file_name,
|
||||
this.filename,
|
||||
this.image,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it('should run the texcount command', function () {
|
||||
this.directory = `${this.Settings.path.compilesDir}/${this.projectId}-${this.userId}`
|
||||
this.filePath = `$COMPILE_DIR/${this.file_name}`
|
||||
this.filePath = `$COMPILE_DIR/${this.filename}`
|
||||
this.command = [
|
||||
'texcount',
|
||||
'-nocol',
|
||||
|
@ -553,7 +543,7 @@ describe('CompileManager', function () {
|
|||
.calledWith(
|
||||
`${this.projectId}-${this.userId}`,
|
||||
this.command,
|
||||
this.directory,
|
||||
this.compileDir,
|
||||
this.image,
|
||||
this.timeout,
|
||||
{}
|
||||
|
|
116
services/clsi/test/unit/js/SynctexOutputParserTests.js
Normal file
116
services/clsi/test/unit/js/SynctexOutputParserTests.js
Normal 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([])
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue