1 /*
2 Copyright (c) 2008-2014 jerome DOT laurens AT u-bourgogne DOT fr
3
4 This file is part of the SyncTeX package.
5
6 Latest Revision: Tue Jan 14 09:55:00 UTC 2014
7
8 Version: 1.17
9
10 License:
11 --------
12 Permission is hereby granted, free of charge, to any person
13 obtaining a copy of this software and associated documentation
14 files (the "Software"), to deal in the Software without
15 restriction, including without limitation the rights to use,
16 copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the
18 Software is furnished to do so, subject to the following
19 conditions:
20
21 The above copyright notice and this permission notice shall be
22 included in all copies or substantial portions of the Software.
23
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31 OTHER DEALINGS IN THE SOFTWARE
32
33 Except as contained in this notice, the name of the copyright holder
34 shall not be used in advertising or otherwise to promote the sale,
35 use or other dealings in this Software without prior written
36 authorization from the copyright holder.
37
38 Acknowledgments:
39 ----------------
40 The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh,
41 and significant help from XeTeX developer Jonathan Kew
42
43 Nota Bene:
44 ----------
45 If you include or use a significant part of the synctex package into a software,
46 I would appreciate to be listed as contributor and see "SyncTeX" highlighted.
47
48 Version 1.2
49 Thu Jun 19 09:39:21 UTC 2008
50
51 History:
52 --------
53
54 - the -d option for an input directory
55
56 Important notice:
57 -----------------
58 This file is named "synctex_main.c".
59 This is the command line interface to the synctex_parser.c.
60 */
61
62 #ifdef __linux__
63 #define _ISOC99_SOURCE /* to get the fmax() prototype */
64 #endif
65
66 # include <w2c/c-auto.h> /* for inline && HAVE_xxx */
67
68 # include <stdlib.h>
69 # include <stdio.h>
70 # include <string.h>
71 # include <stdarg.h>
72 # include <math.h>
73 # include "synctex_parser.h"
74 # include "synctex_parser_utils.h"
75
76 /* The code below uses strlcat and strlcpy, which avoids security warnings with some compilers.
77 However, if these are not available we simply use the old, unchecked versions;
78 this is OK because all the uses in this code are working with a buffer that's been
79 allocated based on measuring the strings involved. */
80 # ifndef HAVE_STRLCAT
81 # define strlcat(dst, src, size) strcat((dst), (src))
82 # endif
83 # ifndef HAVE_STRLCPY
84 # define strlcpy(dst, src, size) strcpy((dst), (src))
85 # endif
86 # ifndef HAVE_FMAX
87 # define fmax my_fmax
my_fmax(double x,double y)88 inline static double my_fmax(double x, double y) { return (x < y) ? y : x; }
89 # endif
90
91 #ifdef WIN32
92 # define snprintf _snprintf
93 #endif
94
95 #if SYNCTEX_DEBUG
96 # ifdef WIN32
97 # include <direct.h>
98 # define getcwd _getcwd
99 # else
100 # include <unistd.h>
101 # endif
102 #endif
103
104 int main(int argc, char *argv[]);
105
106 void synctex_help(const char * error,...);
107 void synctex_help_view(const char * error,...);
108 void synctex_help_edit(const char * error,...);
109 void synctex_help_update(const char * error,...);
110
111 int synctex_view(int argc, char *argv[]);
112 int synctex_edit(int argc, char *argv[]);
113 int synctex_update(int argc, char *argv[]);
114 int synctex_test(int argc, char *argv[]);
115
main(int argc,char * argv[])116 int main(int argc, char *argv[])
117 {
118 int arg_index = 1;
119 printf("This is SyncTeX command line utility, version 1.2\n");
120 if(arg_index<argc) {
121 if(0==strcmp("help",argv[arg_index])) {
122 if(++arg_index<argc) {
123 if(0==strcmp("view",argv[arg_index])) {
124 synctex_help_view(NULL);
125 return 0;
126 } else if(0==strcmp("edit",argv[arg_index])) {
127 synctex_help_edit(NULL);
128 return 0;
129 } else if(0==strcmp("update",argv[arg_index])) {
130 synctex_help_update(NULL);
131 return 0;
132 }
133 }
134 synctex_help(NULL);
135 return 0;
136 } else if(0==strcmp("view",argv[arg_index])) {
137 return synctex_view(argc-arg_index-1,argv+arg_index+1);
138 } else if(0==strcmp("edit",argv[arg_index])) {
139 return synctex_edit(argc-arg_index-1,argv+arg_index+1);
140 } else if(0==strcmp("update",argv[arg_index])) {
141 return synctex_update(argc-arg_index-1,argv+arg_index+1);
142 } else if(0==strcmp("test",argv[arg_index])) {
143 return synctex_test(argc-arg_index-1,argv+arg_index+1);
144 }
145 }
146 synctex_help("Missing options");
147 return 0;
148 }
149
synctex_usage(const char * error,va_list ap)150 static void synctex_usage(const char * error,va_list ap) {
151 if(error) {
152 fprintf(stderr,"SyncTeX ERROR: ");
153 vfprintf(stderr,error,ap);
154 fprintf(stderr,"\n");
155 }
156 fprintf((error?stderr:stdout),
157 "usage: synctex <subcommand> [options] [args]\n"
158 "Synchronize TeXnology command-line client, version 1.17\n\n"
159 "The Synchronization TeXnology by Jérôme Laurens is a new feature of recent TeX engines.\n"
160 "It allows to synchronize between input and output, which means to\n"
161 "navigate from the source document to the typeset material and vice versa.\n\n"
162 );
163 return;
164 }
165
synctex_help(const char * error,...)166 void synctex_help(const char * error,...) {
167 va_list v;
168 va_start(v, error);
169 synctex_usage(error, v);
170 va_end(v);
171 fprintf((error?stderr:stdout),
172 "Available subcommands:\n"
173 " view to perform forwards synchronization\n"
174 " edit to perform backwards synchronization\n"
175 " update to update a synctex file after a dvi/xdv to pdf filter\n"
176 " help this help\n\n"
177 "Type 'synctex help <subcommand>' for help on a specific subcommand.\n"
178 "There is also an undocumented test subcommand.\n"
179 );
180 return;
181 }
182
synctex_help_view(const char * error,...)183 void synctex_help_view(const char * error,...) {
184 va_list v;
185 va_start(v, error);
186 synctex_usage(error, v);
187 va_end(v);
188 fputs("synctex view: forwards or direct synchronization,\n"
189 "command sent by the editor to view the output corresponding to the position under the mouse\n"
190 "\n"
191 "usage: synctex view -i line:column:input -o output [-d directory] [-x viewer-command] [-h before/offset:middle/after]\n"
192 "\n"
193 "-i line:column:input\n"
194 " specify the line, column and input file.\n"
195 " The line and column are 1 based integers,\n"
196 " they allow to identify every character in a file.\n"
197 " column is the offset of a character relative to the containing line.\n"
198 " Pass 0 if this information is not relevant.\n"
199 " input is either the name of the main source file or an included document.\n"
200 " It must be the very name as understood by TeX, id est the name exactly as it appears in the log file.\n"
201 " It does not matter if the file actually exists or not, except that the command is not really useful.\n"
202 " \n"
203 "-o output\n"
204 " is the full or relative path of the output file (with any relevant path extension).\n"
205 " This file must exist.\n"
206 " \n"
207 "-d directory\n"
208 " is the directory containing the synctex file, in case it is different from the directory of the output.\n"
209 " This directory must exist.\n"
210 " An example will explain how things work: for synctex -o ...:bar.tex -d foo,\n"
211 " the chosen synctex file is the most recent among bar.synctex, bar.synctex.gz, foo/bar.synctex and foo/bar.synctex.gz.\n"
212 " The other ones are simply removed, if the authorization is granted\n"
213 " \n"
214 "-x viewer-command\n"
215 " Normally the synctex tool outputs its result to the stdout.\n"
216 " It is possible to launch an external tool with the result.\n"
217 " The viewer-command is a printf like format string with following specifiers.\n"
218 " %{output} is the name specifier of the main document, without path extension.\n"
219 " %{page} is the 0 based page number specifier, %{page+1} is the 1 based page number specifier.\n"
220 " To synchronize by point, %{x} is the x coordinate specifier, %{y} is the y coordinate specifier,\n"
221 " both in dots and relative to the top left corner of the page.\n"
222 " To synchronize by box,\n"
223 " %{h} is the horizontal coordinate specifier of the origin of the enclosing box,\n"
224 " %{v} is the vertical coordinate specifier of the origin of the enclosing box,\n"
225 " both in dots and relative to the upper left corner of the page.\n"
226 " They may be different from the preceding pair of coordinates.\n"
227 " %{width} is the width specifier, %{height} is the height specifier of the enclosing box.\n"
228 " The latter dimension is naturally counted from bottom to top.\n"
229 " There is no notion of depth for such a box.\n"
230 " To synchronize by content, %{before} is the word before,\n"
231 " %{offset} is the offset specifier, %{middle} is the middle word, and %{after} is the word after.\n"
232 "\n"
233 " If no viewer command is provided, the content of the SYNCTEX_VIEWER environment variable is used instead.\n"
234 "\n"
235 "-h before/offset:middle/after\n"
236 " This hint allows a forwards synchronization by contents.\n"
237 " Instead of giving a character offset in a line, you can give full words.\n"
238 " A full word is a sequence of characters (excepting '/').\n"
239 " You will choose full words in the source document that will certainly appear unaltered in the output.\n"
240 " The \"middle\" word contains the character under the mouse at position offset.\n"
241 " \"before\" is a full word preceding middle and \"after\" is following it.\n"
242 " The before or after word can be missing, they are then considered as void strings.\n"
243 " \n"
244 "The result is a list of records. In general the first one is the most accurate but\n"
245 "it is the responsibility of the client to decide which one best fits the user needs.\n",
246 (error?stderr:stdout)
247 );
248 return;
249 }
250
251 typedef struct {
252 int line;
253 int column;
254 unsigned int offset;
255 char * input;
256 char * output;
257 char * directory;
258 char * viewer;
259 char * before;
260 char * middle;
261 char * after;
262 } synctex_view_params_t;
263
264 int synctex_view_proceed(synctex_view_params_t * paramsRef);
265
266 /* "usage: synctex view -i line:column:input -o output [-d directory] [-x viewer-command] [-h before/offset:middle/after]\n" */
synctex_view(int argc,char * argv[])267 int synctex_view(int argc, char *argv[]) {
268 int arg_index = 0;
269 char * start = NULL;
270 char * end = NULL;
271 synctex_view_params_t Ps = {-1,0,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
272
273 /* required */
274 if((arg_index>=argc) || strcmp("-i",argv[arg_index]) || (++arg_index>=argc)) {
275 synctex_help_view("Missing -i required argument");
276 return -1;
277 }
278 start = argv[arg_index];
279 Ps.line = strtol(start,&end,10);
280 if(end>start && strlen(end)>0 && *end==':') {
281 start = end+1;
282 Ps.column = strtol(start,&end,10);
283 if(end == start || Ps.column < 0) {
284 Ps.column = 0;
285 }
286 if(strlen(end)>1 && *end==':') {
287 Ps.input = end+1;
288 goto scan_output;
289 }
290 }
291 synctex_help_view("Bad -i argument");
292 return -1;
293 scan_output:
294 if((++arg_index>=argc) || strcmp("-o",argv[arg_index]) || (++arg_index>=argc)) {
295 synctex_help_view("Missing -o required argument");
296 return -1;
297 }
298 Ps.output = argv[arg_index];
299 /* now scan the optional arguments */
300 if(++arg_index<argc) {
301 if(0 == strcmp("-d",argv[arg_index])) {
302 if(++arg_index<argc) {
303 Ps.directory = argv[arg_index];
304 if(++arg_index<argc) {
305 goto option_command;
306 } else {
307 return synctex_view_proceed(&Ps);
308 }
309 } else {
310 Ps.directory = getenv("SYNCTEX_BUILD_DIRECTORY");
311 return synctex_view_proceed(&Ps);
312 }
313 }
314 option_command:
315 if(0 == strcmp("-x",argv[arg_index])) {
316 if(++arg_index<argc) {
317 if(strcmp("-",argv[arg_index])) {
318 /* next option does not start with '-', this is a command */
319 Ps.viewer = argv[arg_index];
320 if(++arg_index<argc) {
321 goto option_hint;
322 } else {
323 return synctex_view_proceed(&Ps);
324 }
325 } else {
326 /* retrieve the environment variable */
327 Ps.viewer = getenv("SYNCTEX_VIEWER");
328 goto option_hint;
329 }
330 } else {
331 Ps.viewer = getenv("SYNCTEX_VIEWER");
332 return synctex_view_proceed(&Ps);
333 }
334 }
335 option_hint:
336 if(0 == strcmp("-h",argv[arg_index]) && ++arg_index<argc) {
337 /* modify the argument */;
338 Ps.after = strstr(argv[arg_index],"/");
339 if(NULL != Ps.after) {
340 Ps.before = argv[arg_index];
341 *Ps.after = '\0';
342 ++Ps.after;
343 Ps.offset = strtoul(Ps.after,&Ps.middle,10);
344 if(Ps.middle>Ps.after && strlen(Ps.middle)>2) {
345 Ps.after = strstr(++Ps.middle,"/");
346 if(NULL != Ps.after) {
347 *Ps.after = '\0';
348 if(Ps.offset<strlen(Ps.middle)) {
349 ++Ps.after;
350 return synctex_view_proceed(&Ps);
351 }
352 }
353 }
354 }
355 synctex_help_view("Bad hint");
356 return -1;
357 }
358 }
359 return synctex_view_proceed(&Ps);
360 }
361
synctex_view_proceed(synctex_view_params_t * Ps)362 int synctex_view_proceed(synctex_view_params_t * Ps) {
363 synctex_scanner_t scanner = NULL;
364 size_t size = 0;
365 #if SYNCTEX_DEBUG
366 printf("line:%i\n",Ps->line);
367 printf("column:%i\n",Ps->column);
368 printf("input:%s\n",Ps->input);
369 printf("viewer:%s\n",Ps->viewer);
370 printf("before:%s\n",Ps->before);
371 printf("offset:%i\n",Ps->offset);
372 printf("middle:%s\n",Ps->middle);
373 printf("after:%s\n",Ps->after);
374 printf("output:%s\n",Ps->output);
375 printf("cwd:%s\n",getcwd(NULL,0));
376 #endif
377 /* We assume that viewer is not so big: */
378 # define SYNCTEX_STR_SIZE 65536
379 if(Ps->viewer && strlen(Ps->viewer)>=SYNCTEX_STR_SIZE) {
380 synctex_help_view("Viewer command is too long");
381 return -1;
382 }
383 scanner = synctex_scanner_new_with_output_file(Ps->output,Ps->directory,1);
384 if(scanner && synctex_display_query(scanner,Ps->input,Ps->line,Ps->column)) {
385 synctex_node_t node = NULL;
386 if((node = synctex_next_result(scanner)) != NULL) {
387 /* filtering the command */
388 if(Ps->viewer && strlen(Ps->viewer)) {
389 char * viewer = Ps->viewer;
390 char * where = NULL;
391 char * buffer = NULL;
392 char * buffer_cur = NULL;
393 int printed = 0;
394 int status = 0;
395 /* Preparing the buffer where everything will be printed */
396 size = strlen(viewer)+3*sizeof(int)+6*sizeof(float)+4*(SYNCTEX_STR_SIZE);
397 buffer = malloc(size+1);
398 if(NULL == buffer) {
399 synctex_help_view("No memory available");
400 return -1;
401 }
402 /* Properly terminate the buffer, no bad access for string related functions. */
403 buffer[size] = '\0';
404 /* Replace %{ by &{, then remove all unescaped '%'*/
405 while((where = strstr(viewer,"%{")) != NULL) {
406 *where = '&';
407 }
408 /* find all the unescaped '%', change to a safe character */
409 where = viewer;
410 while(where && (where = strstr(where,"%"))) {
411 /* Find the next occurrence of a "%",
412 * if it is not followed by another "%",
413 * replace it by a "&" */
414 if(strlen(++where)) {
415 if(*where == '%') {
416 ++where;
417 } else {
418 *(where-1)='&';
419 }
420 }
421 }
422 buffer_cur = buffer;
423 /* find the next occurrence of a format key */
424 where = viewer;
425 while(viewer && (where = strstr(viewer,"&{"))) {
426 #define TEST(KEY,FORMAT,WHAT)\
427 if(!strncmp(where,KEY,strlen(KEY))) {\
428 printed = where-viewer;\
429 if(buffer_cur != memcpy(buffer_cur,viewer,(size_t)printed)) {\
430 synctex_help_view("Memory copy problem");\
431 free(buffer);\
432 return -1;\
433 }\
434 buffer_cur += printed;size-=printed;\
435 printed = snprintf(buffer_cur,size,FORMAT,WHAT);\
436 if((unsigned)printed >= (unsigned)size) {\
437 synctex_help_view("Snprintf problem");\
438 free(buffer);\
439 return -1;\
440 }\
441 buffer_cur += printed;size-=printed;\
442 *buffer_cur='\0';\
443 viewer = where+strlen(KEY);\
444 continue;\
445 }
446 TEST("&{output}","%s",synctex_scanner_get_output(scanner));
447 TEST("&{page}", "%i",synctex_node_page(node)-1);
448 TEST("&{page+1}","%i",synctex_node_page(node));
449 TEST("&{x}", "%f",synctex_node_visible_h(node));
450 TEST("&{y}", "%f",synctex_node_visible_v(node));
451 TEST("&{h}", "%f",synctex_node_box_visible_h(node));
452 TEST("&{v}", "%f",synctex_node_box_visible_v(node)+synctex_node_box_visible_depth(node));
453 TEST("&{width}", "%f",fabs(synctex_node_box_visible_width(node)));
454 TEST("&{height}","%f",fmax(synctex_node_box_visible_height(node)+synctex_node_box_visible_depth(node),1));
455 TEST("&{before}","%s",(Ps->before && strlen(Ps->before)<SYNCTEX_STR_SIZE?Ps->before:""));
456 TEST("&{offset}","%i",Ps->offset);
457 TEST("&{middle}","%s",(Ps->middle && strlen(Ps->middle)<SYNCTEX_STR_SIZE?Ps->middle:""));
458 TEST("&{after}", "%s",(Ps->after && strlen(Ps->after)<SYNCTEX_STR_SIZE?Ps->after:""));
459 #undef TEST
460 break;
461 }
462 /* copy the rest of viewer into the buffer */
463 if(buffer_cur != strncpy(buffer_cur,viewer,size + 1)) {
464 synctex_help_view("Memory copy problem");
465 free(buffer);
466 return -1;
467 }
468 buffer_cur[size] = '\0';
469 printf("SyncTeX: Executing\n%s\n",buffer);
470 status = system(buffer);
471 free(buffer);
472 buffer = NULL;
473 return status;
474 } else {
475 /* just print out the results */
476 puts("SyncTeX result begin");
477 do {
478 printf( "Output:%s\n"
479 "Page:%i\n"
480 "x:%f\n"
481 "y:%f\n"
482 "h:%f\n"
483 "v:%f\n"
484 "W:%f\n"
485 "H:%f\n"
486 "before:%s\n"
487 "offset:%i\n"
488 "middle:%s\n"
489 "after:%s\n",
490 Ps->output,
491 synctex_node_page(node),
492 synctex_node_visible_h(node),
493 synctex_node_visible_v(node),
494 synctex_node_box_visible_h(node),
495 synctex_node_box_visible_v(node)+synctex_node_box_visible_depth(node),
496 synctex_node_box_visible_width(node),
497 synctex_node_box_visible_height(node)+synctex_node_box_visible_depth(node),
498 (Ps->before?Ps->before:""),
499 Ps->offset,
500 (Ps->middle?Ps->middle:""),
501 (Ps->after?Ps->after:""));
502 } while((node = synctex_next_result(scanner)) != NULL);
503 puts("SyncTeX result end");
504 }
505 }
506 }
507 return 0;
508 }
509
synctex_help_edit(const char * error,...)510 void synctex_help_edit(const char * error,...) {
511 va_list v;
512 va_start(v, error);
513 synctex_usage(error, v);
514 va_end(v);
515 fputs(
516 "synctex edit: backwards or reverse synchronization,\n"
517 "command sent by the viewer to edit the source corresponding to the position under the mouse\n\n"
518 "\n"
519 "usage: synctex edit -o page:x:y:file [-d directory] [-x editor-command] [-h offset:context]\n"
520 "\n"
521 "-o page:x:y:file\n"
522 " specify the page and coordinates of the point under the mouse.\n"
523 " page is 1 based.\n"
524 " Coordinates x and y are counted from the top left corner of the page.\n"
525 " Their unit is the big point (72 dpi).\n"
526 " \n"
527 " file is in general the path of a pdf or dvi file.\n"
528 " It can be either absolute or relative to the current directory.\n"
529 " This named file must always exist.\n"
530 " \n"
531 "-d directory\n"
532 " is the directory containing the synctex file, in case it is different from the directory of the output.\n"
533 " This directory must exist.\n"
534 " An example will explain how things work: for synctex -o ...:bar.tex -d foo,\n"
535 " the chosen synctex file is the most recent among bar.synctex, bar.synctex.gz, foo/bar.synctex and foo/bar.synctex.gz.\n"
536 " The other ones are simply removed, if the authorization is granted\n"
537 " \n"
538 "-x editor-command\n"
539 " Normally the synctex tool outputs its result to the stdout.\n"
540 " It is possible to execute an external tool with the result of the query.\n"
541 " The editor-command is a printf like format string with following specifiers.\n"
542 " They will be replaced by their value before the command is executed.\n"
543 " %{output} is the full path specifier of the output document, with no extension.\n"
544 " %{input} is the name specifier of the input document.\n"
545 " %{line} is the 0 based line number specifier. %{line+1} is the 1 based line number specifier.\n"
546 " %{column} is the 0 based column number specifier or -1. %{column+1} is the 1 based column number or -1.\n"
547 " %{offset} is the 0 based offset specifier and %{context} is the context specifier of the hint.\n"
548 " \n"
549 " If no editor-command is provided, the content of the SYNCTEX_EDITOR environment variable is used instead.\n"
550 " \n"
551 "-h offset:context\n"
552 " This hint allows a backwards or reverse synchronization by contents.\n"
553 " You give a context including the character at the mouse location, and\n"
554 " the offset of this character relative to the beginning of this bunch of text.\n"
555 " \n",
556 (error?stderr:stdout)
557 );
558 return;
559 }
560
561 typedef struct {
562 int page;
563 float x;
564 float y;
565 unsigned int offset;
566 char * output;
567 char * directory;
568 char * editor;
569 char * context;
570 } synctex_edit_params_t;
571
572 int synctex_edit_proceed(synctex_edit_params_t * Ps);
573
574 /* "usage: synctex edit -o page:x:y:output [-d directory] [-x editor-command] [-h offset:context]\n" */
synctex_edit(int argc,char * argv[])575 int synctex_edit(int argc, char *argv[]) {
576 int arg_index = 0;
577 char * start = NULL;
578 char * end = NULL;
579 synctex_edit_params_t Ps = {0,0,0,0,NULL,NULL,NULL,NULL};
580 /* required */
581 if((arg_index>=argc) || strcmp("-o",argv[arg_index]) || (++arg_index>=argc)) {
582 synctex_help_edit("Missing -o required argument");
583 return -1;
584 }
585 start = argv[arg_index];
586 Ps.page = strtol(start,&end,10);
587 if(end>start && strlen(end)>1 && *end==':') {
588 start = end+1;
589 Ps.x = strtod(start,&end);
590 if(end>start && strlen(end)>1 && *end==':') {
591 start = end+1;
592 Ps.y = strtod(start,&end);
593 if(end>start && strlen(end)>1 && *end==':') {
594 Ps.output = ++end;
595 goto scan_execute;
596 }
597 }
598 }
599 synctex_help_edit("Bad -o argument");
600 return -1;
601 scan_execute:
602 /* now scan the optional arguments */
603 if(++arg_index<argc) {
604 if(0 == strcmp("-d",argv[arg_index])) {
605 if(++arg_index<argc) {
606 Ps.directory = argv[arg_index];
607 if(++arg_index<argc) {
608 goto option_command;
609 } else {
610 return synctex_edit_proceed(&Ps);
611 }
612 } else {
613 Ps.directory = getenv("SYNCTEX_BUILD_DIRECTORY");
614 return synctex_edit_proceed(&Ps);
615 }
616 }
617 option_command:
618 if(0 == strcmp("-x",argv[arg_index])) {
619 if(++arg_index<argc) {
620 if(strcmp("-",argv[arg_index])) {
621 /* next option does not start with '-', this is a command */
622 Ps.editor = argv[arg_index];
623 if(++arg_index<argc) {
624 goto option_hint;
625 } else {
626 return synctex_edit_proceed(&Ps);
627 }
628 } else {
629 /* retrieve the environment variable */
630 Ps.editor = getenv("SYNCTEX_EDITOR");
631 goto option_hint;
632 }
633 } else {
634 Ps.editor = getenv("SYNCTEX_EDITOR");
635 return synctex_edit_proceed(&Ps);
636 }
637 }
638 option_hint:
639 if(0 == strcmp("-h",argv[arg_index]) && ++arg_index<argc) {
640
641 start = argv[arg_index];
642 end = NULL;
643 Ps.offset = strtol(start,&end,10);
644 if(end>start && strlen(end)>1 && *end==':') {
645 Ps.context = end+1;
646 return synctex_edit_proceed(&Ps);
647 }
648 synctex_help_edit("Bad -h argument");
649 return -1;
650 }
651 }
652 return synctex_edit_proceed(&Ps);
653 }
654
synctex_edit_proceed(synctex_edit_params_t * Ps)655 int synctex_edit_proceed(synctex_edit_params_t * Ps) {
656 synctex_scanner_t scanner = NULL;
657 #if SYNCTEX_DEBUG
658 printf("page:%i\n",Ps->page);
659 printf("x:%f\n",Ps->x);
660 printf("y:%f\n",Ps->y);
661 printf("almost output:%s\n",Ps->output);
662 printf("editor:%s\n",Ps->editor);
663 printf("offset:%i\n",Ps->offset);
664 printf("context:%s\n",Ps->context);
665 printf("cwd:%s\n",getcwd(NULL,0));
666 #endif
667 scanner = synctex_scanner_new_with_output_file(Ps->output,Ps->directory,1);
668 if(NULL == scanner) {
669 synctex_help_edit("No SyncTeX available for %s",Ps->output);
670 return -1;
671 }
672 if(synctex_edit_query(scanner,Ps->page,Ps->x,Ps->y)) {
673 synctex_node_t node = NULL;
674 const char * input = NULL;
675 if(NULL != (node = synctex_next_result(scanner))
676 && NULL != (input = synctex_scanner_get_name(scanner,synctex_node_tag(node)))) {
677 /* filtering the command */
678 if(Ps->editor && strlen(Ps->editor)) {
679 size_t size = 0;
680 char * where = NULL;
681 char * buffer = NULL;
682 char * buffer_cur = NULL;
683 int printed;
684 int status;
685 size = strlen(Ps->editor)+3*sizeof(int)+3*SYNCTEX_STR_SIZE;
686 buffer = malloc(size+1);
687 if(NULL == buffer) {
688 printf("SyncTeX ERROR: No memory available\n");
689 return -1;
690 }
691 buffer[size]='\0';
692 /* Replace %{ by &{, then remove all unescaped '%'*/
693 while((where = strstr(Ps->editor,"%{")) != NULL) {
694 *where = '&';
695 }
696 where = Ps->editor;
697 while(where &&(where = strstr(where,"%"))) {
698 if(strlen(++where)) {
699 if(*where == '%') {
700 ++where;
701 } else {
702 *(where-1)='&';
703 }
704 }
705 }
706 buffer_cur = buffer;
707 /* find the next occurrence of a format key */
708 where = Ps->editor;
709 while(Ps->editor && (where = strstr(Ps->editor,"&{"))) {
710 #define TEST(KEY,FORMAT,WHAT)\
711 if(!strncmp(where,KEY,strlen(KEY))) {\
712 printed = where-Ps->editor;\
713 if(buffer_cur != memcpy(buffer_cur,Ps->editor,(size_t)printed)) {\
714 synctex_help_edit("Memory copy problem");\
715 free(buffer);\
716 return -1;\
717 }\
718 buffer_cur += printed;size-=printed;\
719 printed = snprintf(buffer_cur,size,FORMAT,WHAT);\
720 if((unsigned)printed >= (unsigned)size) {\
721 synctex_help_edit("Snprintf problem");\
722 free(buffer);\
723 return -1;\
724 }\
725 buffer_cur += printed;size-=printed;\
726 *buffer_cur='\0';\
727 Ps->editor = where+strlen(KEY);\
728 continue;\
729 }
730 TEST("&{output}", "%s",Ps->output);
731 TEST("&{input}", "%s",input);
732 TEST("&{line}", "%i",synctex_node_line(node));
733 TEST("&{column}", "%i",-1);
734 TEST("&{offset}", "%i",Ps->offset);
735 TEST("&{context}","%s",Ps->context);
736 #undef TEST
737 break;
738 }
739 /* copy the rest of editor into the buffer */
740 if(buffer_cur != memcpy(buffer_cur,Ps->editor,strlen(Ps->editor))) {
741 fputs("! synctex_edit: Memory copy problem",stderr);
742 free(buffer);
743 return -1;
744 }\
745 printf("SyncTeX: Executing\n%s\n",buffer);
746 status = system(buffer);
747 free(buffer);
748 buffer = NULL;
749 return status;
750 } else {
751 /* just print out the results */
752 puts("SyncTeX result begin");
753 do {
754 printf( "Output:%s\n"
755 "Input:%s\n"
756 "Line:%i\n"
757 "Column:%i\n"
758 "Offset:%i\n"
759 "Context:%s\n",
760 Ps->output,
761 input,
762 synctex_node_line(node),
763 synctex_node_column(node),
764 Ps->offset,
765 (Ps->context?Ps->context:""));
766 } while((node = synctex_next_result(scanner)) != NULL);
767 puts("SyncTeX result end");
768 }
769 }
770 }
771 return 0;
772 }
773
synctex_help_update(const char * error,...)774 void synctex_help_update(const char * error,...) {
775 va_list v;
776 va_start(v, error);
777 synctex_usage(error, v);
778 va_end(v);
779 fputs(
780 "synctex update: up to date synctex file,\n"
781 "Use this command to update the synctex file once a dvi/xdv to pdf filter is applied.\n\n"
782 "\n"
783 "usage: synctex update -o output [-d directory] [-m number] [-x dimension] [-y dimension]\n"
784 "\n"
785 "-o output is the full or relative path of an existing file,\n"
786 " either the real synctex file you wish to update\n"
787 " or a related file: foo.tex, foo.pdf, foo.dvi...\n"
788 "-d directory is the directory containing the synctex file, in case it is different from the directory of the output.\n"
789 " This directory must exist.\n"
790 " An example will explain how things work: for synctex -o ...:bar.tex -d foo,\n"
791 " the chosen synctex file is the most recent among bar.synctex, bar.synctex.gz, foo/bar.synctex and foo/bar.synctex.gz.\n"
792 " The other ones are simply removed, if the authorization is granted\n"
793 " \n"
794 "-m number Set additional magnification\n"
795 "-x dimension Set horizontal offset\n"
796 "-y dimension Set vertical offset\n"
797 "In general, these are exactly the same options provided to the dvi/xdv to pdf filter.\n",
798 (error?stderr:stdout)
799 );
800 return;
801 }
802
803 /* "usage: synctex update -o output [-d directory] [-m number] [-x dimension] [-y dimension]\n" */
synctex_update(int argc,char * argv[])804 int synctex_update(int argc, char *argv[]) {
805 int arg_index = 0;
806 synctex_updater_t updater = NULL;
807 char * magnification = NULL;
808 char * x = NULL;
809 char * y = NULL;
810 char * output = NULL;
811 char * directory = NULL;
812 #define SYNCTEX_fprintf (*synctex_fprintf)
813 if(arg_index>=argc) {
814 synctex_help_update("Bad update command");
815 return -1;
816 }
817 /* required */
818 if((arg_index>=argc) || strcmp("-o",argv[arg_index]) || (++arg_index>=argc)) {
819 synctex_help_update("Missing -o required argument");
820 return -1;
821 }
822 output = argv[arg_index];
823 if(++arg_index>=argc) {
824 return 0;
825 }
826 next_argument:
827 if(0 == strcmp("-m",argv[arg_index])) {
828 if(++arg_index>=argc) {
829 synctex_help_update("Missing magnification");
830 return -1;
831 }
832 magnification = argv[arg_index];
833 prepare_next_argument:
834 if(++arg_index<argc) {
835 goto next_argument;
836 }
837 } else if(0 == strcmp("-x",argv[arg_index])) {
838 if(++arg_index>=argc) {
839 synctex_help_update("Missing x offset");
840 return -1;
841 }
842 x = argv[arg_index];
843 goto prepare_next_argument;
844 } else if(0 == strcmp("-y",argv[arg_index])) {
845 if(++arg_index>=argc) {
846 synctex_help_update("Missing y offset");
847 return -1;
848 }
849 y = argv[arg_index];
850 goto prepare_next_argument;
851 } else if(0 == strcmp("-d",argv[arg_index])) {
852 if(++arg_index<argc) {
853 directory = argv[arg_index];
854 } else {
855 directory = getenv("SYNCTEX_BUILD_DIRECTORY");
856 }
857 goto prepare_next_argument;
858 }
859
860 /* Arguments parsed */
861 updater = synctex_updater_new_with_output_file(output,directory);
862 synctex_updater_append_magnification(updater,magnification);
863 synctex_updater_append_x_offset(updater,x);
864 synctex_updater_append_y_offset(updater,y);
865 synctex_updater_free(updater);
866 return 0;
867 }
868
869 int synctex_test_file (int argc, char *argv[]);
870
871 /* "usage: synctex test subcommand options\n" */
synctex_test(int argc,char * argv[])872 int synctex_test(int argc, char *argv[]) {
873 if(argc) {
874 if(0==strcmp("file",argv[0])) {
875 return synctex_test_file(argc-1,argv+1);
876 }
877 }
878 return 0;
879 }
880
synctex_test_file(int argc,char * argv[])881 int synctex_test_file (int argc, char *argv[])
882 {
883 int arg_index = 0;
884 char * output = NULL;
885 char * directory = NULL;
886 char * synctex_name = NULL;
887 synctex_compress_mode_t mode = synctex_compress_mode_none;
888 if(arg_index>=argc) {
889 _synctex_error("! usage: synctex test file -o output [-d directory]\n");
890 return -1;
891 }
892 /* required */
893 if((arg_index>=argc) || strcmp("-o",argv[arg_index]) || (++arg_index>=argc)) {
894 _synctex_error("! usage: synctex test file -o output [-d directory]\n");
895 return -1;
896 }
897 output = argv[arg_index];
898 /* optional */
899 if(++arg_index<argc) {
900 if(0 == strcmp("-d",argv[arg_index])) {
901 if(++arg_index<argc) {
902 directory = argv[arg_index];
903 } else {
904 directory = getenv("SYNCTEX_BUILD_DIRECTORY");
905 }
906 }
907 }
908 /* Arguments parsed */
909 if(_synctex_get_name(output, directory, &synctex_name, &mode)) {
910 _synctex_error("! TEST FAILED\n");
911 } else {
912 printf("output:%s\n"
913 "directory:%s\n"
914 "file name:%s\n"
915 "compression mode:%s\n",
916 output,
917 directory,
918 synctex_name,
919 (mode?"gz":"none"));
920 }
921 return 0;
922 }
923