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