mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Add in a synctex end point
This commit is contained in:
parent
129cf0bf62
commit
892e6bf6d1
19 changed files with 5760 additions and 5 deletions
1
services/clsi/.gitignore
vendored
1
services/clsi/.gitignore
vendored
|
@ -13,3 +13,4 @@ cache
|
||||||
.vagrant
|
.vagrant
|
||||||
db.sqlite
|
db.sqlite
|
||||||
config/*
|
config/*
|
||||||
|
bin/synctex
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
spawn = require("child_process").spawn
|
||||||
|
|
||||||
module.exports = (grunt) ->
|
module.exports = (grunt) ->
|
||||||
grunt.initConfig
|
grunt.initConfig
|
||||||
coffee:
|
coffee:
|
||||||
|
@ -40,7 +42,6 @@ module.exports = (grunt) ->
|
||||||
acceptance_tests: ["test/acceptance/js"]
|
acceptance_tests: ["test/acceptance/js"]
|
||||||
smoke_tests: ["test/smoke/js"]
|
smoke_tests: ["test/smoke/js"]
|
||||||
|
|
||||||
|
|
||||||
execute:
|
execute:
|
||||||
app:
|
app:
|
||||||
src: "app.js"
|
src: "app.js"
|
||||||
|
@ -69,6 +70,14 @@ module.exports = (grunt) ->
|
||||||
grunt.loadNpmTasks 'grunt-execute'
|
grunt.loadNpmTasks 'grunt-execute'
|
||||||
grunt.loadNpmTasks 'grunt-bunyan'
|
grunt.loadNpmTasks 'grunt-bunyan'
|
||||||
|
|
||||||
|
grunt.registerTask 'compile:bin', () ->
|
||||||
|
callback = @async()
|
||||||
|
proc = spawn "cc", [
|
||||||
|
"-o", "bin/synctex", "-lz", "-Isrc/synctex",
|
||||||
|
"src/synctex.c", "src/synctex/synctex_parser.c", "src/synctex/synctex_parser_utils.c"
|
||||||
|
], stdio: "inherit"
|
||||||
|
proc.on "close", callback
|
||||||
|
|
||||||
grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests']
|
grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests']
|
||||||
grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute']
|
grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute']
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ app = express()
|
||||||
app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile
|
app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile
|
||||||
app.del "/project/:project_id", CompileController.clearCache
|
app.del "/project/:project_id", CompileController.clearCache
|
||||||
|
|
||||||
|
app.get "/project/:project_id/sync/code", CompileController.syncFromCode
|
||||||
|
app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf
|
||||||
|
|
||||||
staticServer = express.static(Settings.path.compilesDir)
|
staticServer = express.static(Settings.path.compilesDir)
|
||||||
app.get "/project/:project_id/output/*", (req, res, next) ->
|
app.get "/project/:project_id/output/*", (req, res, next) ->
|
||||||
req.url = "/#{req.params.project_id}/#{req.params[0]}"
|
req.url = "/#{req.params.project_id}/#{req.params[0]}"
|
||||||
|
|
|
@ -38,3 +38,27 @@ module.exports = CompileController =
|
||||||
ProjectPersistenceManager.clearProject req.params.project_id, (error) ->
|
ProjectPersistenceManager.clearProject req.params.project_id, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.send 204 # No content
|
res.send 204 # No content
|
||||||
|
|
||||||
|
syncFromCode: (req, res, next = (error) ->) ->
|
||||||
|
file = req.query.file
|
||||||
|
line = parseInt(req.query.line, 10)
|
||||||
|
column = parseInt(req.query.column, 10)
|
||||||
|
project_id = req.params.project_id
|
||||||
|
|
||||||
|
CompileManager.syncFromCode project_id, file, line, column, (error, pdfPositions) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.send JSON.stringify {
|
||||||
|
pdf: pdfPositions
|
||||||
|
}
|
||||||
|
|
||||||
|
syncFromPdf: (req, res, next = (error) ->) ->
|
||||||
|
page = parseInt(req.query.page, 10)
|
||||||
|
h = parseFloat(req.query.h)
|
||||||
|
v = parseFloat(req.query.v)
|
||||||
|
project_id = req.params.project_id
|
||||||
|
|
||||||
|
CompileManager.syncFromPdf project_id, page, h, v, (error, codePositions) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.send JSON.stringify {
|
||||||
|
code: codePositions
|
||||||
|
}
|
||||||
|
|
|
@ -52,3 +52,66 @@ module.exports = CompileManager =
|
||||||
return callback(null)
|
return callback(null)
|
||||||
else
|
else
|
||||||
return callback(new Error("rm -r #{compileDir} failed: #{stderr}"))
|
return callback(new Error("rm -r #{compileDir} failed: #{stderr}"))
|
||||||
|
|
||||||
|
syncFromCode: (project_id, file_name, line, column, callback = (error, pdfPositions) ->) ->
|
||||||
|
# If LaTeX was run in a virtual environment, the file path that synctex expects
|
||||||
|
# might not match the file path on the host. The .synctex.gz file however, will be accessed
|
||||||
|
# wherever it is on the host.
|
||||||
|
base_dir = Settings.path.synctexBaseDir(project_id)
|
||||||
|
file_path = Path.join(base_dir, file_name)
|
||||||
|
synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf")
|
||||||
|
CompileManager._runSynctex ["code", synctex_path, file_path, line, column], (error, stdout) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
logger.log project_id: project_id, file_name: file_name, line: line, column: column, stdout: stdout, "synctex code output"
|
||||||
|
callback null, CompileManager._parseSynctexFromCodeOutput(stdout)
|
||||||
|
|
||||||
|
syncFromPdf: (project_id, page, h, v, callback = (error, filePositions) ->) ->
|
||||||
|
base_dir = Settings.path.synctexBaseDir(project_id)
|
||||||
|
synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf")
|
||||||
|
CompileManager._runSynctex ["pdf", synctex_path, page, h, v], (error, stdout) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
logger.log project_id: project_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output"
|
||||||
|
callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir)
|
||||||
|
|
||||||
|
_runSynctex: (args, callback = (error, stdout) ->) ->
|
||||||
|
bin_path = Path.resolve(__dirname + "/../../bin/synctex")
|
||||||
|
proc = child_process.spawn bin_path, args
|
||||||
|
proc.on "error", callback
|
||||||
|
|
||||||
|
stdout = ""
|
||||||
|
proc.stdout.on "data", (chunk) -> stdout += chunk.toString()
|
||||||
|
stderr = ""
|
||||||
|
proc.stderr.on "data", (chunk) -> stderr += chunk.toString()
|
||||||
|
|
||||||
|
proc.on "close", (code) ->
|
||||||
|
if code == 0
|
||||||
|
return callback(null, stdout)
|
||||||
|
else
|
||||||
|
return callback(new Error("synctex failed: #{stderr}"))
|
||||||
|
|
||||||
|
_parseSynctexFromCodeOutput: (output) ->
|
||||||
|
results = []
|
||||||
|
for line in output.split("\n")
|
||||||
|
[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
|
||||||
|
|
||||||
|
_parseSynctexFromPdfOutput: (output, base_dir) ->
|
||||||
|
results = []
|
||||||
|
for line in output.split("\n")
|
||||||
|
[node, file_path, line, column] = line.split("\t")
|
||||||
|
if node == "NODE"
|
||||||
|
file = file_path.slice(base_dir.length + 1)
|
||||||
|
results.push {
|
||||||
|
file: file
|
||||||
|
line: parseInt(line, 10)
|
||||||
|
column: parseInt(column, 10)
|
||||||
|
}
|
||||||
|
return results
|
|
@ -33,25 +33,25 @@ module.exports = LatexRunner =
|
||||||
|
|
||||||
_pdflatexCommand: (mainFile) ->
|
_pdflatexCommand: (mainFile) ->
|
||||||
LatexRunner._latexmkBaseCommand.concat [
|
LatexRunner._latexmkBaseCommand.concat [
|
||||||
"-pdf", "-e", "$pdflatex='pdflatex -interaction=batchmode %O %S'",
|
"-pdf", "-e", "$pdflatex='pdflatex -synctex=1 -interaction=batchmode %O %S'",
|
||||||
Path.join("$COMPILE_DIR", mainFile)
|
Path.join("$COMPILE_DIR", mainFile)
|
||||||
]
|
]
|
||||||
|
|
||||||
_latexCommand: (mainFile) ->
|
_latexCommand: (mainFile) ->
|
||||||
LatexRunner._latexmkBaseCommand.concat [
|
LatexRunner._latexmkBaseCommand.concat [
|
||||||
"-pdfdvi", "-e", "$latex='latex -interaction=batchmode %O %S'",
|
"-pdfdvi", "-e", "$latex='latex -synctex=1 -interaction=batchmode %O %S'",
|
||||||
Path.join("$COMPILE_DIR", mainFile)
|
Path.join("$COMPILE_DIR", mainFile)
|
||||||
]
|
]
|
||||||
|
|
||||||
_xelatexCommand: (mainFile) ->
|
_xelatexCommand: (mainFile) ->
|
||||||
LatexRunner._latexmkBaseCommand.concat [
|
LatexRunner._latexmkBaseCommand.concat [
|
||||||
"-xelatex", "-e", "$pdflatex='xelatex -interaction=batchmode %O %S'",
|
"-xelatex", "-e", "$pdflatex='xelatex -synctex=1 -interaction=batchmode %O %S'",
|
||||||
Path.join("$COMPILE_DIR", mainFile)
|
Path.join("$COMPILE_DIR", mainFile)
|
||||||
]
|
]
|
||||||
|
|
||||||
_lualatexCommand: (mainFile) ->
|
_lualatexCommand: (mainFile) ->
|
||||||
LatexRunner._latexmkBaseCommand.concat [
|
LatexRunner._latexmkBaseCommand.concat [
|
||||||
"-pdf", "-e", "$pdflatex='lualatex -interaction=batchmode %O %S'",
|
"-pdf", "-e", "$pdflatex='lualatex -synctex=1 -interaction=batchmode %O %S'",
|
||||||
Path.join("$COMPILE_DIR", mainFile)
|
Path.join("$COMPILE_DIR", mainFile)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ module.exports =
|
||||||
path:
|
path:
|
||||||
compilesDir: Path.resolve(__dirname + "/../compiles")
|
compilesDir: Path.resolve(__dirname + "/../compiles")
|
||||||
clsiCacheDir: Path.resolve(__dirname + "/../cache")
|
clsiCacheDir: Path.resolve(__dirname + "/../cache")
|
||||||
|
synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id)
|
||||||
|
|
||||||
internal:
|
internal:
|
||||||
clsi:
|
clsi:
|
||||||
|
|
66
services/clsi/src/synctex.c
Normal file
66
services/clsi/src/synctex.c
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#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;
|
||||||
|
}
|
4249
services/clsi/src/synctex/synctex_parser.c
Normal file
4249
services/clsi/src/synctex/synctex_parser.c
Normal file
File diff suppressed because it is too large
Load diff
346
services/clsi/src/synctex/synctex_parser.h
Normal file
346
services/clsi/src/synctex/synctex_parser.h
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
/*
|
||||||
|
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
|
45
services/clsi/src/synctex/synctex_parser_local.h
Normal file
45
services/clsi/src/synctex/synctex_parser_local.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
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
|
141
services/clsi/src/synctex/synctex_parser_readme.txt
Normal file
141
services/clsi/src/synctex/synctex_parser_readme.txt
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
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
|
||||||
|
|
479
services/clsi/src/synctex/synctex_parser_utils.c
Normal file
479
services/clsi/src/synctex/synctex_parser_utils.c
Normal file
|
@ -0,0 +1,479 @@
|
||||||
|
/*
|
||||||
|
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];
|
||||||
|
}
|
141
services/clsi/src/synctex/synctex_parser_utils.h
Normal file
141
services/clsi/src/synctex/synctex_parser_utils.h
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
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
services/clsi/src/synctex/synctex_parser_version.txt
Normal file
1
services/clsi/src/synctex/synctex_parser_version.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1.16
|
38
services/clsi/test/acceptance/coffee/SynctexTests.coffee
Normal file
38
services/clsi/test/acceptance/coffee/SynctexTests.coffee
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
Client = require "./helpers/Client"
|
||||||
|
request = require "request"
|
||||||
|
require("chai").should()
|
||||||
|
expect = require("chai").expect
|
||||||
|
|
||||||
|
describe "Syncing", ->
|
||||||
|
before (done) ->
|
||||||
|
@request =
|
||||||
|
resources: [
|
||||||
|
path: "main.tex"
|
||||||
|
content: '''
|
||||||
|
\\documentclass{article}
|
||||||
|
\\begin{document}
|
||||||
|
Hello world
|
||||||
|
\\end{document}
|
||||||
|
'''
|
||||||
|
]
|
||||||
|
@project_id = Client.randomId()
|
||||||
|
Client.compile @project_id, @request, (@error, @res, @body) => done()
|
||||||
|
|
||||||
|
describe "from code to pdf", ->
|
||||||
|
it "should return the correct location", (done) ->
|
||||||
|
Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) ->
|
||||||
|
throw error if error?
|
||||||
|
expect(pdfPositions).to.deep.equal(
|
||||||
|
pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ]
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "from pdf to code", ->
|
||||||
|
it "should return the correct location", (done) ->
|
||||||
|
Client.syncFromPdf @project_id, 1, 100, 200, (error, codePositions) ->
|
||||||
|
throw error if error?
|
||||||
|
expect(codePositions).to.deep.equal(
|
||||||
|
code: [ { file: 'main.tex', line: 3, column: -1 } ]
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
|
|
@ -32,6 +32,29 @@ module.exports = Client =
|
||||||
app.use express.static(directory)
|
app.use express.static(directory)
|
||||||
app.listen(port, host)
|
app.listen(port, host)
|
||||||
|
|
||||||
|
syncFromCode: (project_id, file, line, column, callback = (error, pdfPositions) ->) ->
|
||||||
|
request.get {
|
||||||
|
url: "#{@host}/project/#{project_id}/sync/code"
|
||||||
|
qs: {
|
||||||
|
file: file
|
||||||
|
line: line
|
||||||
|
column: column
|
||||||
|
}
|
||||||
|
}, (error, response, body) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback null, JSON.parse(body)
|
||||||
|
|
||||||
|
syncFromPdf: (project_id, page, h, v, callback = (error, pdfPositions) ->) ->
|
||||||
|
request.get {
|
||||||
|
url: "#{@host}/project/#{project_id}/sync/pdf"
|
||||||
|
qs: {
|
||||||
|
page: page,
|
||||||
|
h: h, v: v
|
||||||
|
}
|
||||||
|
}, (error, response, body) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback null, JSON.parse(body)
|
||||||
|
|
||||||
compileDirectory: (project_id, baseDirectory, directory, serverPort, callback = (error, res, body) ->) ->
|
compileDirectory: (project_id, baseDirectory, directory, serverPort, callback = (error, res, body) ->) ->
|
||||||
resources = []
|
resources = []
|
||||||
entities = fs.readdirSync("#{baseDirectory}/#{directory}")
|
entities = fs.readdirSync("#{baseDirectory}/#{directory}")
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe "CompileController", ->
|
||||||
@Settings.externalUrl = "http://www.example.com"
|
@Settings.externalUrl = "http://www.example.com"
|
||||||
@req = {}
|
@req = {}
|
||||||
@res = {}
|
@res = {}
|
||||||
|
@next = sinon.stub()
|
||||||
|
|
||||||
describe "compile", ->
|
describe "compile", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@ -90,3 +91,61 @@ describe "CompileController", ->
|
||||||
)
|
)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
describe "syncFromCode", ->
|
||||||
|
beforeEach ->
|
||||||
|
@file = "main.tex"
|
||||||
|
@line = 42
|
||||||
|
@column = 5
|
||||||
|
@project_id = "mock-project-id"
|
||||||
|
@req.params =
|
||||||
|
project_id: @project_id
|
||||||
|
@req.query =
|
||||||
|
file: @file
|
||||||
|
line: @line.toString()
|
||||||
|
column: @column.toString()
|
||||||
|
@res.send = sinon.stub()
|
||||||
|
|
||||||
|
@CompileManager.syncFromCode = sinon.stub().callsArgWith(4, null, @pdfPositions = ["mock-positions"])
|
||||||
|
@CompileController.syncFromCode @req, @res, @next
|
||||||
|
|
||||||
|
it "should find the corresponding location in the PDF", ->
|
||||||
|
@CompileManager.syncFromCode
|
||||||
|
.calledWith(@project_id, @file, @line, @column)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should return the positions", ->
|
||||||
|
@res.send
|
||||||
|
.calledWith(JSON.stringify
|
||||||
|
pdf: @pdfPositions
|
||||||
|
)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
describe "syncFromPdf", ->
|
||||||
|
beforeEach ->
|
||||||
|
@page = 5
|
||||||
|
@h = 100.23
|
||||||
|
@v = 45.67
|
||||||
|
@project_id = "mock-project-id"
|
||||||
|
@req.params =
|
||||||
|
project_id: @project_id
|
||||||
|
@req.query =
|
||||||
|
page: @page.toString()
|
||||||
|
h: @h.toString()
|
||||||
|
v: @v.toString()
|
||||||
|
@res.send = sinon.stub()
|
||||||
|
|
||||||
|
@CompileManager.syncFromPdf = sinon.stub().callsArgWith(4, null, @codePositions = ["mock-positions"])
|
||||||
|
@CompileController.syncFromPdf @req, @res, @next
|
||||||
|
|
||||||
|
it "should find the corresponding location in the code", ->
|
||||||
|
@CompileManager.syncFromPdf
|
||||||
|
.calledWith(@project_id, @page, @h, @v)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should return the positions", ->
|
||||||
|
@res.send
|
||||||
|
.calledWith(JSON.stringify
|
||||||
|
code: @codePositions
|
||||||
|
)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ require('chai').should()
|
||||||
modulePath = require('path').join __dirname, '../../../app/js/CompileManager'
|
modulePath = require('path').join __dirname, '../../../app/js/CompileManager'
|
||||||
tk = require("timekeeper")
|
tk = require("timekeeper")
|
||||||
EventEmitter = require("events").EventEmitter
|
EventEmitter = require("events").EventEmitter
|
||||||
|
Path = require "path"
|
||||||
|
|
||||||
describe "CompileManager", ->
|
describe "CompileManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@ -102,3 +103,68 @@ describe "CompileManager", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
@callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id} failed: #{@error}"
|
@callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id} failed: #{@error}"
|
||||||
|
|
||||||
|
describe "syncing", ->
|
||||||
|
beforeEach ->
|
||||||
|
@page = 1
|
||||||
|
@h = 42.23
|
||||||
|
@v = 87.56
|
||||||
|
@width = 100.01
|
||||||
|
@height = 234.56
|
||||||
|
@line = 5
|
||||||
|
@column = 3
|
||||||
|
@file_name = "main.tex"
|
||||||
|
@proc = new EventEmitter()
|
||||||
|
@proc.stdout = new EventEmitter()
|
||||||
|
@proc.stderr = new EventEmitter()
|
||||||
|
@child_process.spawn = sinon.stub().returns(@proc)
|
||||||
|
@Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}"
|
||||||
|
|
||||||
|
describe "syncFromCode", ->
|
||||||
|
beforeEach ->
|
||||||
|
@stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n"
|
||||||
|
@CompileManager.syncFromCode @project_id, @file_name, @line, @column, @callback
|
||||||
|
@proc.stdout.emit "data", @stdout
|
||||||
|
@proc.emit "close", 0
|
||||||
|
|
||||||
|
it "should execute the synctex binary", ->
|
||||||
|
bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
|
||||||
|
synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf"
|
||||||
|
file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}"
|
||||||
|
@child_process.spawn
|
||||||
|
.calledWith(bin_path, ["code", synctex_path, file_path, @line, @column])
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback with the parsed output", ->
|
||||||
|
@callback
|
||||||
|
.calledWith(null, [{
|
||||||
|
page: @page
|
||||||
|
h: @h
|
||||||
|
v: @v
|
||||||
|
height: @height
|
||||||
|
width: @width
|
||||||
|
}])
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
describe "syncFromPdf", ->
|
||||||
|
beforeEach ->
|
||||||
|
@stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}\t#{@line}\t#{@column}\n"
|
||||||
|
@CompileManager.syncFromPdf @project_id, @page, @h, @v, @callback
|
||||||
|
@proc.stdout.emit "data", @stdout
|
||||||
|
@proc.emit "close", 0
|
||||||
|
|
||||||
|
it "should execute the synctex binary", ->
|
||||||
|
bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
|
||||||
|
synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf"
|
||||||
|
@child_process.spawn
|
||||||
|
.calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v])
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback with the parsed output", ->
|
||||||
|
@callback
|
||||||
|
.calledWith(null, [{
|
||||||
|
file: @file_name
|
||||||
|
line: @line
|
||||||
|
column: @column
|
||||||
|
}])
|
||||||
|
.should.equal true
|
Loading…
Reference in a new issue