Exemple #1
0
/**
 * ev_document_synctex_backward_search:
 * @document: a #EvDocument
 * @page_index: the target page
 * @x: X coordinate
 * @y: Y coordinate
 *
 * Peforms a Synctex backward search to obtain the TeX input file, line and
 * (possibly) column  corresponding to the  position (@x,@y) (in 72dpi
 * coordinates) in the  @page of @document.
 *
 * Returns: A pointer to the EvSourceLink structure that holds the result. @NULL if synctex
 * is not enabled for the document or no result is found.
 * The EvSourceLink pointer should be freed with g_free after it is used.
 */
EvSourceLink *
ev_document_synctex_backward_search (EvDocument *document,
                                     gint        page_index,
                                     gfloat      x,
                                     gfloat      y)
{
        EvSourceLink *result = NULL;
        synctex_scanner_t scanner;

        g_return_val_if_fail (EV_IS_DOCUMENT (document), NULL);

        scanner = document->priv->synctex_scanner;
        if (!scanner)
                return NULL;

        if (synctex_edit_query (scanner, page_index + 1, x, y) > 0) {
                synctex_node_t node;

                /* We assume that a backward search returns either zero or one result_node */
                node = synctex_next_result (scanner);
                if (node != NULL) {
			const gchar *filename;

			filename = synctex_scanner_get_name (scanner, synctex_node_tag (node));
			
			if (filename) {
				result = ev_source_link_new (filename,
							     synctex_node_line (node),
							     synctex_node_column (node));
			}
                }
        }

        return result;
}
Exemple #2
0
/**
 * ev_document_synctex_forward_search:
 * @document: a #EvDocument
 * @source_link: a #EvSourceLink
 *
 * Peforms a Synctex forward search to obtain the area in the document
 * corresponding to the position @line and @column number in the source Tex file
 *
 * Returns: An EvMapping with the page number and area corresponfing to
 * the given line in the source file. It must be free with g_free when done
 */
EvMapping *
ev_document_synctex_forward_search (EvDocument   *document,
				    EvSourceLink *link)
{
        EvMapping        *result = NULL;
        synctex_scanner_t scanner;

        g_return_val_if_fail (EV_IS_DOCUMENT (document), NULL);

        scanner = document->priv->synctex_scanner;
        if (!scanner)
                return NULL;

        if (synctex_display_query (scanner, link->filename, link->line, link->col) > 0) {
                synctex_node_t node;
                gint           page;

                if ((node = synctex_next_result (scanner))) {
                        result = g_new (EvMapping, 1);

                        page = synctex_node_page (node) - 1;
                        result->data = GINT_TO_POINTER (page);

                        result->area.x1 = synctex_node_box_visible_h (node);
                        result->area.y1 = synctex_node_box_visible_v (node) -
                                synctex_node_box_visible_height (node);
                        result->area.x2 = synctex_node_box_visible_width (node) + result->area.x1;
                        result->area.y2 = synctex_node_box_visible_depth (node) +
                                synctex_node_box_visible_height (node) + result->area.y1;
                }
        }

        return result;
}
Exemple #3
0
void SynctexHandler::synctexClick(const QPointF &pagePos, int pageNumber)
{
	if (!m_synctexScanner)
		return;

	if (synctex_edit_query(m_synctexScanner, pageNumber + 1, pagePos.x(), pagePos.y()) > 0)
	{
		synctex_node_t synctexNode;
		while ((synctexNode = synctex_next_result(m_synctexScanner)) != 0)
		{
			const QString texFileName = QString::fromUtf8(synctex_scanner_get_name(m_synctexScanner, synctex_node_tag(synctexNode)));
			const QDir currentDir(QFileInfo(m_fileName).canonicalPath());
			Q_EMIT openTexDocument(QFileInfo(currentDir, texFileName).canonicalFilePath(), synctex_node_line(synctexNode));
		}
	}
}
Exemple #4
0
QList<SynctexTextBox> SynctexHandler::syncFromSource(const QString &sourceFile, int lineNumber)
{
	QList<SynctexTextBox> textBoxList;

	if (!m_synctexScanner)
		return textBoxList;

	// find the name synctex is using for this file
	const QFileInfo sourceFileInfo(sourceFile);
	const QDir currentDir(QFileInfo(m_fileName).canonicalPath());
	synctex_node_t synctexNode = synctex_scanner_input(m_synctexScanner);
	QString texFileName;
	bool found = false;
	while (synctexNode)
	{
		texFileName = QString::fromUtf8(synctex_scanner_get_name(m_synctexScanner, synctex_node_tag(synctexNode)));
		if (QFileInfo(currentDir, texFileName) == sourceFileInfo)
		{
			found = true;
			break;
		}
		synctexNode = synctex_node_sibling(synctexNode);
	}
	if (!found)
		return textBoxList;

	if (synctex_display_query(m_synctexScanner, texFileName.toUtf8().data(), lineNumber, 0) > 0)
	{
		int pageNumber = -1;
		while ((synctexNode = synctex_next_result(m_synctexScanner)) != 0)
		{
			if (pageNumber < 0)
				pageNumber = synctex_node_page(synctexNode);
			if (synctex_node_page(synctexNode) != pageNumber)
				continue;
			const QRectF textBox(synctex_node_box_visible_h(synctexNode),
			    synctex_node_box_visible_v(synctexNode) - synctex_node_box_visible_height(synctexNode),
			    synctex_node_box_visible_width(synctexNode),
			    synctex_node_box_visible_height(synctexNode));
			SynctexTextBox synctexTextBox;
			synctexTextBox.pageNumber = pageNumber;
			synctexTextBox.textBox = textBox;
			textBoxList << synctexTextBox;
		}
	}
	return textBoxList;
}
Exemple #5
0
bool
synctex_get_input_line_column(const char* filename, unsigned int page, int x, int y,
    char** input_file, unsigned int* line, unsigned int* column)
{
  if (filename == NULL) {
    return false;
  }

  synctex_scanner_t scanner = synctex_scanner_new_with_output_file(filename, NULL, 1);
  if (scanner == NULL) {
    girara_debug("Failed to create synctex scanner.");
    return false;
  }

  synctex_scanner_t temp = synctex_scanner_parse(scanner);
  if (temp == NULL) {
    girara_debug("Failed to parse synctex file.");
    synctex_scanner_free(scanner);
    return false;
  }

  bool ret = false;

  if (synctex_edit_query(scanner, page + 1u, x, y) > 0) {
    /* Assume that a backward search returns at most one result. */
    synctex_node_t node = synctex_next_result(scanner);
    if (node != NULL) {
      if (input_file != NULL) {
        *input_file = g_strdup(synctex_scanner_get_name(scanner, synctex_node_tag(node)));
      }
      if (line != NULL) {
        *line = synctex_node_line(node);
      }
      if (column != NULL) {
        *column = synctex_node_column(node);
      }

      ret = true;
    }
  }

  synctex_scanner_free(scanner);

  return ret;
}
Exemple #6
0
int SyncTex::DocToSource(UINT pageNo, PointI pt, ScopedMem<WCHAR>& filename, UINT *line, UINT *col)
{
    if (IsIndexDiscarded()) {
        if (RebuildIndex() != PDFSYNCERR_SUCCESS)
            return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED;
    }
    CrashIf(!this->scanner);

    // Coverity: at this point, this->scanner->flags.has_parsed == 1 and thus
    // synctex_scanner_parse never gets the chance to freeing the scanner
    if (synctex_edit_query(this->scanner, pageNo, (float)pt.x, (float)pt.y) <= 0)
        return PDFSYNCERR_NO_SYNC_AT_LOCATION;

    synctex_node_t node = synctex_next_result(this->scanner);
    if (!node)
        return PDFSYNCERR_NO_SYNC_AT_LOCATION;

    const char *name = synctex_scanner_get_name(this->scanner, synctex_node_tag(node));
    if (!name)
        return PDFSYNCERR_UNKNOWN_SOURCEFILE;

    bool isUtf8 = true;
    filename.Set(str::conv::FromUtf8(name));
TryAgainAnsi:
    if (!filename)
        return PDFSYNCERR_OUTOFMEMORY;

    // undecorate the filepath: replace * by space and / by \ 
    str::TransChars(filename, L"*/", L" \\");
    // Convert the source filepath to an absolute path
    if (PathIsRelative(filename))
        filename.Set(PrependDir(filename));

    // recent SyncTeX versions encode in UTF-8 instead of ANSI
    if (isUtf8 && !file::Exists(filename)) {
        isUtf8 = false;
        filename.Set(str::conv::FromAnsi(name));
        goto TryAgainAnsi;
    }

    *line = synctex_node_line(node);
    *col = synctex_node_column(node);

    return PDFSYNCERR_SUCCESS;
}
int SyncTex::SourceToDoc(const WCHAR* srcfilename, UINT line, UINT col, UINT *page, Vec<RectI> &rects)
{
    if (IsIndexDiscarded())
        if (RebuildIndex() != PDFSYNCERR_SUCCESS)
            return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED;
    assert(this->scanner);

    ScopedMem<WCHAR> srcfilepath;
    // convert the source file to an absolute path
    if (PathIsRelative(srcfilename))
        srcfilepath.Set(PrependDir(srcfilename));
    else
        srcfilepath.Set(str::Dup(srcfilename));
    if (!srcfilepath)
        return PDFSYNCERR_OUTOFMEMORY;

    bool isUtf8 = true;
    char *mb_srcfilepath = str::conv::ToUtf8(srcfilepath);
TryAgainAnsi:
    if (!mb_srcfilepath)
        return PDFSYNCERR_OUTOFMEMORY;
    int ret = synctex_display_query(this->scanner, mb_srcfilepath, line, col);
    free(mb_srcfilepath);
    // recent SyncTeX versions encode in UTF-8 instead of ANSI
    if (isUtf8 && -1 == ret) {
        isUtf8 = false;
        mb_srcfilepath = str::conv::ToAnsi(srcfilepath);
        goto TryAgainAnsi;
    }

    if (-1 == ret)
        return PDFSYNCERR_UNKNOWN_SOURCEFILE;
    if (0 == ret)
        return PDFSYNCERR_NOSYNCPOINT_FOR_LINERECORD;

    synctex_node_t node;
    int firstpage = -1;
    rects.Reset();

    while ((node = synctex_next_result(this->scanner)) != NULL) {
        if (firstpage == -1) {
            firstpage = synctex_node_page(node);
            if (firstpage <= 0 || firstpage > engine->PageCount())
                continue;
            *page = (UINT)firstpage;
        }
        if (synctex_node_page(node) != firstpage)
            continue;

        RectD rc;
        rc.x  = synctex_node_box_visible_h(node);
        rc.y  = synctex_node_box_visible_v(node) - synctex_node_box_visible_height(node);
        rc.dx = synctex_node_box_visible_width(node),
        rc.dy = synctex_node_box_visible_height(node) + synctex_node_box_visible_depth(node);
        rects.Push(rc.Round());
    }

    if (firstpage <= 0)
        return PDFSYNCERR_NOSYNCPOINT_FOR_LINERECORD;
    return PDFSYNCERR_SUCCESS;
}
Exemple #8
0
int synctex_edit_proceed(synctex_edit_params_t * Ps) {
	synctex_scanner_t scanner = NULL;
#if SYNCTEX_DEBUG
	printf("page:%i\n",Ps->page);
	printf("x:%f\n",Ps->x);
	printf("y:%f\n",Ps->y);
	printf("almost output:%s\n",Ps->output);
	printf("editor:%s\n",Ps->editor);
	printf("offset:%i\n",Ps->offset);
	printf("context:%s\n",Ps->context);
	printf("cwd:%s\n",getcwd(NULL,0));
#endif
	scanner = synctex_scanner_new_with_output_file(Ps->output,Ps->directory,1);
	if(NULL == scanner) {
		synctex_help_edit("No SyncTeX available for %s",Ps->output);
		return -1;
	}
	if(synctex_edit_query(scanner,Ps->page,Ps->x,Ps->y)) {
		synctex_node_t node = NULL;
		const char * input = NULL;
		if(NULL != (node = synctex_next_result(scanner))
				&& NULL != (input = synctex_scanner_get_name(scanner,synctex_node_tag(node)))) {
			/* filtering the command */
			if(Ps->editor && strlen(Ps->editor)) {
				size_t size = 0;
				char * where = NULL;
				char * buffer = NULL;
				char * buffer_cur = NULL;
				int printed;
				int status;
				size = strlen(Ps->editor)+3*sizeof(int)+3*SYNCTEX_STR_SIZE;
				buffer = malloc(size+1);
				if(NULL == buffer) {
					printf("SyncTeX ERROR: No memory available\n");
					return -1;
				}
				buffer[size]='\0';
				/* Replace %{ by &{, then remove all unescaped '%'*/
				while((where = strstr(Ps->editor,"%{")) != NULL) {
					*where = '&';
				}
				where = Ps->editor;
				while(where &&(where = strstr(where,"%"))) {
					if(strlen(++where)) {
						if(*where == '%') {
							++where;
						} else {
							*(where-1)='&';
						}
					}
				}
				buffer_cur = buffer;
				/*  find the next occurrence of a format key */
				where = Ps->editor;
				while(Ps->editor && (where = strstr(Ps->editor,"&{"))) {
					#define TEST(KEY,FORMAT,WHAT)\
					if(!strncmp(where,KEY,strlen(KEY))) {\
						printed = where-Ps->editor;\
						if(buffer_cur != memcpy(buffer_cur,Ps->editor,(size_t)printed)) {\
							synctex_help_edit("Memory copy problem");\
							free(buffer);\
							return -1;\
						}\
						buffer_cur += printed;size-=printed;\
						printed = snprintf(buffer_cur,size,FORMAT,WHAT);\
						if((unsigned)printed >= (unsigned)size) {\
							synctex_help_edit("Snprintf problem");\
							free(buffer);\
							return -1;\
						}\
						buffer_cur += printed;size-=printed;\
						*buffer_cur='\0';\
						Ps->editor = where+strlen(KEY);\
						continue;\
					}
					TEST("&{output}", "%s",Ps->output);
					TEST("&{input}",  "%s",input);
					TEST("&{line}",   "%i",synctex_node_line(node));
					TEST("&{column}", "%i",-1);
					TEST("&{offset}", "%i",Ps->offset);
					TEST("&{context}","%s",Ps->context);
					#undef TEST
					break;
				}
				/* copy the rest of editor into the buffer */
				if(buffer_cur != memcpy(buffer_cur,Ps->editor,strlen(Ps->editor))) {
					fputs("!  synctex_edit: Memory copy problem",stderr);
					free(buffer);
					return -1;
				}\
				printf("SyncTeX: Executing\n%s\n",buffer);
				status = system(buffer);
				free(buffer);
				buffer = NULL;
				return status;
			} else {
				/* just print out the results */
				puts("SyncTeX result begin");
				do {
					printf(	"Output:%s\n"
							"Input:%s\n"
							"Line:%i\n"
							"Column:%i\n"
							"Offset:%i\n"
							"Context:%s\n",
							Ps->output,
							input,
							synctex_node_line(node),
							synctex_node_column(node),
							Ps->offset,
							(Ps->context?Ps->context:""));
				} while((node = synctex_next_result(scanner)) != NULL);
				puts("SyncTeX result end");
			}
		}
	}
	return 0;
}
Exemple #9
0
int synctex_view_proceed(synctex_view_params_t * Ps) {
	synctex_scanner_t scanner = NULL;
	size_t size = 0;
#if SYNCTEX_DEBUG
	printf("line:%i\n",Ps->line);
	printf("column:%i\n",Ps->column);
	printf("input:%s\n",Ps->input);
	printf("viewer:%s\n",Ps->viewer);
	printf("before:%s\n",Ps->before);
	printf("offset:%i\n",Ps->offset);
	printf("middle:%s\n",Ps->middle);
	printf("after:%s\n",Ps->after);
	printf("output:%s\n",Ps->output);
	printf("cwd:%s\n",getcwd(NULL,0));
#endif
	/*  We assume that viewer is not so big: */
#   define SYNCTEX_STR_SIZE 65536
	if(Ps->viewer && strlen(Ps->viewer)>=SYNCTEX_STR_SIZE) {
		synctex_help_view("Viewer command is too long");
		return -1;
	}
	scanner = synctex_scanner_new_with_output_file(Ps->output,Ps->directory,1);
	if(scanner && synctex_display_query(scanner,Ps->input,Ps->line,Ps->column)) {
		synctex_node_t node = NULL;
		if((node = synctex_next_result(scanner)) != NULL) {
			/* filtering the command */
			if(Ps->viewer && strlen(Ps->viewer)) {
				char * viewer = Ps->viewer;
				char * where = NULL;
				char * buffer = NULL;
				char * buffer_cur = NULL;
				int printed = 0;
				int status = 0;
				/* Preparing the buffer where everything will be printed */
				size = strlen(viewer)+3*sizeof(int)+6*sizeof(float)+4*(SYNCTEX_STR_SIZE);
				buffer = malloc(size+1);
				if(NULL == buffer) {
					synctex_help_view("No memory available");
					return -1;
				}
				/*  Properly terminate the buffer, no bad access for string related functions. */
				buffer[size] = '\0';
				/* Replace %{ by &{, then remove all unescaped '%'*/
				while((where = strstr(viewer,"%{")) != NULL) {
					*where = '&';
				}
				/* find all the unescaped '%', change to a safe character */
				where = viewer;
				while(where && (where = strstr(where,"%"))) {
					/*  Find the next occurrence of a "%",
					 *  if it is not followed by another "%",
					 *  replace it by a "&" */
					if(strlen(++where)) {
						if(*where == '%') {
							++where;
						} else {
							*(where-1)='&';
						}
					}
				}
				buffer_cur = buffer;
				/*  find the next occurrence of a format key */
				where = viewer;
				while(viewer && (where = strstr(viewer,"&{"))) {
					#define TEST(KEY,FORMAT,WHAT)\
					if(!strncmp(where,KEY,strlen(KEY))) {\
						printed = where-viewer;\
						if(buffer_cur != memcpy(buffer_cur,viewer,(size_t)printed)) {\
							synctex_help_view("Memory copy problem");\
							free(buffer);\
							return -1;\
						}\
						buffer_cur += printed;size-=printed;\
						printed = snprintf(buffer_cur,size,FORMAT,WHAT);\
						if((unsigned)printed >= (unsigned)size) {\
							synctex_help_view("Snprintf problem");\
							free(buffer);\
							return -1;\
						}\
						buffer_cur += printed;size-=printed;\
						*buffer_cur='\0';\
						viewer = where+strlen(KEY);\
						continue;\
					}
					TEST("&{output}","%s",synctex_scanner_get_output(scanner));
					TEST("&{page}",  "%i",synctex_node_page(node)-1);
					TEST("&{page+1}","%i",synctex_node_page(node));
					TEST("&{x}",     "%f",synctex_node_visible_h(node));
					TEST("&{y}",     "%f",synctex_node_visible_v(node));
					TEST("&{h}",     "%f",synctex_node_box_visible_h(node));
					TEST("&{v}",     "%f",synctex_node_box_visible_v(node)+synctex_node_box_visible_depth(node));
					TEST("&{width}", "%f",fabs(synctex_node_box_visible_width(node)));
					TEST("&{height}","%f",fmax(synctex_node_box_visible_height(node)+synctex_node_box_visible_depth(node),1));
					TEST("&{before}","%s",(Ps->before && strlen(Ps->before)<SYNCTEX_STR_SIZE?Ps->before:""));
					TEST("&{offset}","%i",Ps->offset);
					TEST("&{middle}","%s",(Ps->middle && strlen(Ps->middle)<SYNCTEX_STR_SIZE?Ps->middle:""));
					TEST("&{after}", "%s",(Ps->after && strlen(Ps->after)<SYNCTEX_STR_SIZE?Ps->after:""));
					#undef TEST
					break;
				}
				/* copy the rest of viewer into the buffer */
				if(buffer_cur != strncpy(buffer_cur,viewer,size + 1)) {
					synctex_help_view("Memory copy problem");
					free(buffer);
					return -1;
				}
				buffer_cur[size] = '\0';
				printf("SyncTeX: Executing\n%s\n",buffer);
				status = system(buffer);
				free(buffer);
				buffer = NULL;
				return status;
			} else {
				/* just print out the results */
				puts("SyncTeX result begin");
				do {
					printf(	"Output:%s\n"
							"Page:%i\n"
							"x:%f\n"
							"y:%f\n"
							"h:%f\n"
							"v:%f\n"
							"W:%f\n"
							"H:%f\n"
							"before:%s\n"
							"offset:%i\n"
							"middle:%s\n"
							"after:%s\n",
							Ps->output,
							synctex_node_page(node),
							synctex_node_visible_h(node),
							synctex_node_visible_v(node),
							synctex_node_box_visible_h(node),
							synctex_node_box_visible_v(node)+synctex_node_box_visible_depth(node),
							synctex_node_box_visible_width(node),
							synctex_node_box_visible_height(node)+synctex_node_box_visible_depth(node),
							(Ps->before?Ps->before:""),
							Ps->offset,
							(Ps->middle?Ps->middle:""),
							(Ps->after?Ps->after:""));
				} while((node = synctex_next_result(scanner)) != NULL);
				puts("SyncTeX result end");
			}
		}
	}
	return 0;
}
Exemple #10
0
girara_list_t*
synctex_rectangles_from_position(const char* filename, const char* input_file,
                                 int line, int column, unsigned int* page,
                                 girara_list_t** secondary_rects)
{
  if (filename == NULL || input_file == NULL || page == NULL) {
    return NULL;
  }

  synctex_scanner_t scanner = synctex_scanner_new_with_output_file(filename, NULL, 1);
  if (scanner == NULL) {
    girara_debug("Failed to create synctex scanner.");
    return NULL;
  }

  synctex_scanner_t temp = synctex_scanner_parse(scanner);
  if (temp == NULL) {
    girara_debug("Failed to parse synctex file.");
    synctex_scanner_free(scanner);
    return NULL;
  }

  girara_list_t* hitlist     = girara_list_new2(g_free);
  girara_list_t* other_rects = girara_list_new2(g_free);

  if (synctex_display_query(scanner, input_file, line, column) > 0) {
    synctex_node_t node        = NULL;
    bool got_page              = false;

    while ((node = synctex_next_result (scanner)) != NULL) {
      const unsigned int current_page = synctex_node_page(node) - 1;
      if (got_page == false) {
        got_page = true;
        *page = current_page;
      }

      zathura_rectangle_t rect = { 0, 0, 0, 0 };
      rect.x1 = synctex_node_box_visible_h(node);
      rect.y1 = synctex_node_box_visible_v(node) - synctex_node_box_visible_height(node);
      rect.x2 = rect.x1 + synctex_node_box_visible_width(node);
      rect.y2 = synctex_node_box_visible_depth(node) + synctex_node_box_visible_height (node) + rect.y1;

      if (*page == current_page) {
        zathura_rectangle_t* real_rect = g_try_malloc(sizeof(zathura_rectangle_t));
        if (real_rect == NULL) {
          continue;
        }

        *real_rect = rect;
        girara_list_append(hitlist, real_rect);
      } else {
        synctex_page_rect_t* page_rect = g_try_malloc(sizeof(synctex_page_rect_t));
        if (page_rect == NULL) {
          continue;
        }

        page_rect->page = current_page;
        page_rect->rect = rect;

        girara_list_append(other_rects, page_rect);
      }
    }
  }

  synctex_scanner_free(scanner);

  if (secondary_rects != NULL) {
    *secondary_rects = other_rects;
  } else {
    girara_list_free(other_rects);
  }

  return hitlist;
}
Exemple #11
0
void
synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)
{
  if (zathura == NULL || page == NULL || zathura->synctex.editor == NULL) {
    return;
  }

  zathura_document_t* document = zathura_page_get_document(page);
  if (document == NULL) {
    return;
  }

  const char *filename = zathura_document_get_path(document);
  if (filename == NULL) {
    return;
  }

  synctex_scanner_t scanner = synctex_scanner_new_with_output_file(filename, NULL, 1);
  if (scanner == NULL) {
    girara_debug("Failed to create synctex scanner.");
    return;
  }

  synctex_scanner_t temp = synctex_scanner_parse(scanner);
  if (temp == NULL) {
    girara_debug("Failed to parse synctex file.");
    synctex_scanner_free(scanner);
    return;
  }

  if (synctex_edit_query(scanner, zathura_page_get_index(page) + 1, x, y) > 0) {
    /* Assume that a backward search returns either at most one result. */
    synctex_node_t node = synctex_next_result(scanner);
    if (node != NULL) {
      const char* input_file = synctex_scanner_get_name(scanner, synctex_node_tag(node));
      const int line = synctex_node_line(node);
      const int column = synctex_node_column (node);

      char* linestr = g_strdup_printf("%d", line);
      char* columnstr = g_strdup_printf("%d", column);

      gchar** argv = NULL;
      gint    argc = 0;
      if (g_shell_parse_argv(zathura->synctex.editor, &argc, &argv, NULL) == TRUE) {
        for (gint i = 0; i != argc; ++i) {
          char* temp = girara_replace_substring(argv[i], "%{line}", linestr);
          g_free(argv[i]);
          argv[i] = temp;
          temp = girara_replace_substring(argv[i], "%{column}", columnstr);
          g_free(argv[i]);
          argv[i] = temp;
          temp = girara_replace_substring(argv[i], "%{input}", input_file);
          g_free(argv[i]);
          argv[i] = temp;
        }

        g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
        g_strfreev(argv);
      }

      g_free(linestr);
      g_free(columnstr);
    }
  }

  synctex_scanner_free(scanner);
}