Discussion:
[zathura] [PATCH] synctex forward synchronization
Roland Schatz
2012-08-14 11:05:06 UTC
Permalink
I have attached the patch for forward synchronization.

This patch is a lot more complex than the previous one, and I haven't
really tested it rigorously (although I installed it on my production
system today).

It introduces a new command ":synctex <line>:<column>:<source file>"
(same syntax than the "synctex view -i" parameter). This command runs
synctex, parses the result and displays the result using the existing
"search-results" mechanism.

It introduces one new intltool string (the description of the :synctex
command), and reuses two others (the error messages wrong parameter
count and no document open). I haven't updated the translation yet.


The big open question:
How do you connect that feature to a text editor?

My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
-------------- next part --------------
diff --git a/config.c b/config.c
index 6db7ffc..5d7a956 100644
--- a/config.c
+++ b/config.c
@@ -9,6 +9,7 @@
#include "render.h"
#include "marks.h"
#include "utils.h"
+#include "synctex.h"

#include <girara/settings.h>
#include <girara/session.h>
@@ -308,6 +309,7 @@ config_load_default(zathura_t* zathura)
girara_inputbar_command_add(gsession, "nohlsearch", "nohl", cmd_nohlsearch, NULL, _("Don't highlight current search results"));
girara_inputbar_command_add(gsession, "hlsearch", NULL, cmd_hlsearch, NULL, _("Highlight current search results"));
girara_inputbar_command_add(gsession, "version", NULL, cmd_version, NULL, _("Show version information"));
+ girara_inputbar_command_add(gsession, "synctex", NULL, cmd_synctex, NULL, _("Synctex forward synchronization"));

girara_special_command_add(gsession, '/', cmd_search, true, FORWARD, NULL);
girara_special_command_add(gsession, '?', cmd_search, true, BACKWARD, NULL);
diff --git a/synctex.c b/synctex.c
index 9a9c91f..d4258fb 100644
--- a/synctex.c
+++ b/synctex.c
@@ -1,12 +1,16 @@
/* See LICENSE file for license and copyright information */

+#include <glib.h>
+#include <glib/gi18n.h>
+
#include "synctex.h"

#include "zathura.h"
#include "page.h"
#include "document.h"
+#include "utils.h"

-#include <glib.h>
+#include <girara/session.h>

void
synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)
@@ -29,3 +33,211 @@ synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)

g_free(buffer);
}
+
+static void
+synctex_record_hits(zathura_t* zathura, int page_idx, girara_list_t* hits, bool first)
+{
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_idx-1);
+ if (page == NULL)
+ return;
+
+ GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
+ g_object_set(page_widget, "draw-links", FALSE, NULL);
+ g_object_set(page_widget, "search-results", hits, NULL);
+
+ if (first) {
+ page_set_delayed(zathura, zathura_page_get_index(page));
+ g_object_set(page_widget, "search-current", 0, NULL);
+ }
+}
+
+enum {
+ SYNCTEX_RESULT_BEGIN = 1,
+ SYNCTEX_RESULT_END,
+ SYNCTEX_PROP_PAGE,
+ SYNCTEX_PROP_H,
+ SYNCTEX_PROP_V,
+ SYNCTEX_PROP_WIDTH,
+ SYNCTEX_PROP_HEIGHT,
+};
+
+static struct token {
+ const char* name;
+ guint token;
+} scanner_tokens[] = {
+ {"SyncTeX result begin", SYNCTEX_RESULT_BEGIN},
+ {"SyncTeX result end", SYNCTEX_RESULT_END},
+ {"Page:", SYNCTEX_PROP_PAGE},
+ {"h:", SYNCTEX_PROP_H},
+ {"v:", SYNCTEX_PROP_V},
+ {"W:", SYNCTEX_PROP_WIDTH},
+ {"H:", SYNCTEX_PROP_HEIGHT},
+ {NULL, 0}
+};
+
+static GScannerConfig scanner_config = {
+ .cset_skip_characters = "\n\r",
+ .cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z,
+ .cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z ": ",
+ .cpair_comment_single = NULL,
+ .case_sensitive = TRUE,
+ .scan_identifier = TRUE,
+ .scan_symbols = TRUE,
+ .scan_float = TRUE,
+ .numbers_2_int = TRUE,
+};
+
+static double
+scan_float(GScanner* scanner) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_FLOAT:
+ return g_scanner_cur_value(scanner).v_float;
+ case G_TOKEN_INT:
+ return g_scanner_cur_value(scanner).v_int;
+ default:
+ return 0.0;
+ }
+}
+
+static bool
+synctex_view(zathura_t* zathura, char* position)
+{
+ char* filename = g_strdup(zathura_document_get_path(zathura->document));
+ char* argv[] = {"synctex", "view", "-i", position, "-o", filename, NULL};
+ gint output;
+
+ bool ret = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &output, NULL, NULL);
+ g_free(filename);
+
+ if (!ret) {
+ return false;
+ }
+
+ GScanner* scanner = g_scanner_new(&scanner_config);
+ struct token* tokens = scanner_tokens;
+ while (tokens->name) {
+ g_scanner_add_symbol(scanner, tokens->name, GINT_TO_POINTER(tokens->token));
+ tokens++;
+ }
+
+ g_scanner_input_file(scanner, output);
+
+ bool found_begin = false, found_end = false;
+ while (!found_begin && !found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_BEGIN:
+ found_begin = true;
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (found_begin) {
+ unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
+ for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
+ if (page == NULL) {
+ continue;
+ }
+ g_object_set(zathura_page_get_widget(zathura, page), "search-results", NULL, NULL);
+ }
+ }
+
+ ret = false;
+ int page = -1, nextpage;
+ girara_list_t* hitlist = NULL;
+ zathura_rectangle_t* rectangle = NULL;
+
+ while (!found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_END:
+ found_end = true;
+ break;
+
+ case SYNCTEX_PROP_PAGE:
+ if (g_scanner_get_next_token(scanner) == G_TOKEN_INT) {
+ nextpage = g_scanner_cur_value(scanner).v_int;
+ if (page != nextpage) {
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+ hitlist = girara_list_new2((girara_free_function_t) g_free);
+ page = nextpage;
+ }
+ rectangle = g_malloc0(sizeof(zathura_rectangle_t));
+ girara_list_append(hitlist, rectangle);
+ }
+ break;
+
+ case SYNCTEX_PROP_H:
+ rectangle->x1 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_V:
+ rectangle->y2 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_WIDTH:
+ rectangle->x2 = rectangle->x1 + scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_HEIGHT:
+ rectangle->y1 = rectangle->y2 - scan_float(scanner);
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+
+ g_scanner_destroy(scanner);
+ close(output);
+ return ret;
+}
+
+bool
+cmd_synctex(girara_session_t* session, girara_list_t* argument_list)
+{
+ g_return_val_if_fail(session != NULL, false);
+ g_return_val_if_fail(session->global.data != NULL, false);
+ zathura_t* zathura = session->global.data;
+ if (zathura->document == NULL) {
+ girara_notify(session, GIRARA_ERROR, _("No document opened."));
+ return false;
+ }
+
+ const unsigned int argc = girara_list_size(argument_list);
+ if (argc != 1) {
+ girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
+ return false;
+ }
+
+ char* position = girara_list_nth(argument_list, 0);
+ return synctex_view(zathura, position);
+}
diff --git a/synctex.h b/synctex.h
index 1514117..373f519 100644
--- a/synctex.h
+++ b/synctex.h
@@ -6,5 +6,6 @@
#include "types.h"

void synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y);
+bool cmd_synctex(girara_session_t* session, girara_list_t* argument_list);

#endif
Sebastian Ramacher
2012-08-30 18:52:30 UTC
Permalink
First of all thanks for the patch and I'm sorry that I didn't have the time to
look at the patch yet.
Post by Roland Schatz
How do you connect that feature to a text editor?
My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
What about a dbus interface? Apparently there is a Glib binding for dbus. The
editor could send the messages to zathura via dbus-send.

Another possibility would be an XML-RPC interface but I didn't check if there
are any good and easy-to-use libraries for that.

Kind regards
Roland Schatz
2012-08-14 11:05:06 UTC
Permalink
I have attached the patch for forward synchronization.

This patch is a lot more complex than the previous one, and I haven't
really tested it rigorously (although I installed it on my production
system today).

It introduces a new command ":synctex <line>:<column>:<source file>"
(same syntax than the "synctex view -i" parameter). This command runs
synctex, parses the result and displays the result using the existing
"search-results" mechanism.

It introduces one new intltool string (the description of the :synctex
command), and reuses two others (the error messages wrong parameter
count and no document open). I haven't updated the translation yet.


The big open question:
How do you connect that feature to a text editor?

My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
-------------- next part --------------
diff --git a/config.c b/config.c
index 6db7ffc..5d7a956 100644
--- a/config.c
+++ b/config.c
@@ -9,6 +9,7 @@
#include "render.h"
#include "marks.h"
#include "utils.h"
+#include "synctex.h"

#include <girara/settings.h>
#include <girara/session.h>
@@ -308,6 +309,7 @@ config_load_default(zathura_t* zathura)
girara_inputbar_command_add(gsession, "nohlsearch", "nohl", cmd_nohlsearch, NULL, _("Don't highlight current search results"));
girara_inputbar_command_add(gsession, "hlsearch", NULL, cmd_hlsearch, NULL, _("Highlight current search results"));
girara_inputbar_command_add(gsession, "version", NULL, cmd_version, NULL, _("Show version information"));
+ girara_inputbar_command_add(gsession, "synctex", NULL, cmd_synctex, NULL, _("Synctex forward synchronization"));

girara_special_command_add(gsession, '/', cmd_search, true, FORWARD, NULL);
girara_special_command_add(gsession, '?', cmd_search, true, BACKWARD, NULL);
diff --git a/synctex.c b/synctex.c
index 9a9c91f..d4258fb 100644
--- a/synctex.c
+++ b/synctex.c
@@ -1,12 +1,16 @@
/* See LICENSE file for license and copyright information */

+#include <glib.h>
+#include <glib/gi18n.h>
+
#include "synctex.h"

#include "zathura.h"
#include "page.h"
#include "document.h"
+#include "utils.h"

-#include <glib.h>
+#include <girara/session.h>

void
synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)
@@ -29,3 +33,211 @@ synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)

g_free(buffer);
}
+
+static void
+synctex_record_hits(zathura_t* zathura, int page_idx, girara_list_t* hits, bool first)
+{
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_idx-1);
+ if (page == NULL)
+ return;
+
+ GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
+ g_object_set(page_widget, "draw-links", FALSE, NULL);
+ g_object_set(page_widget, "search-results", hits, NULL);
+
+ if (first) {
+ page_set_delayed(zathura, zathura_page_get_index(page));
+ g_object_set(page_widget, "search-current", 0, NULL);
+ }
+}
+
+enum {
+ SYNCTEX_RESULT_BEGIN = 1,
+ SYNCTEX_RESULT_END,
+ SYNCTEX_PROP_PAGE,
+ SYNCTEX_PROP_H,
+ SYNCTEX_PROP_V,
+ SYNCTEX_PROP_WIDTH,
+ SYNCTEX_PROP_HEIGHT,
+};
+
+static struct token {
+ const char* name;
+ guint token;
+} scanner_tokens[] = {
+ {"SyncTeX result begin", SYNCTEX_RESULT_BEGIN},
+ {"SyncTeX result end", SYNCTEX_RESULT_END},
+ {"Page:", SYNCTEX_PROP_PAGE},
+ {"h:", SYNCTEX_PROP_H},
+ {"v:", SYNCTEX_PROP_V},
+ {"W:", SYNCTEX_PROP_WIDTH},
+ {"H:", SYNCTEX_PROP_HEIGHT},
+ {NULL, 0}
+};
+
+static GScannerConfig scanner_config = {
+ .cset_skip_characters = "\n\r",
+ .cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z,
+ .cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z ": ",
+ .cpair_comment_single = NULL,
+ .case_sensitive = TRUE,
+ .scan_identifier = TRUE,
+ .scan_symbols = TRUE,
+ .scan_float = TRUE,
+ .numbers_2_int = TRUE,
+};
+
+static double
+scan_float(GScanner* scanner) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_FLOAT:
+ return g_scanner_cur_value(scanner).v_float;
+ case G_TOKEN_INT:
+ return g_scanner_cur_value(scanner).v_int;
+ default:
+ return 0.0;
+ }
+}
+
+static bool
+synctex_view(zathura_t* zathura, char* position)
+{
+ char* filename = g_strdup(zathura_document_get_path(zathura->document));
+ char* argv[] = {"synctex", "view", "-i", position, "-o", filename, NULL};
+ gint output;
+
+ bool ret = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &output, NULL, NULL);
+ g_free(filename);
+
+ if (!ret) {
+ return false;
+ }
+
+ GScanner* scanner = g_scanner_new(&scanner_config);
+ struct token* tokens = scanner_tokens;
+ while (tokens->name) {
+ g_scanner_add_symbol(scanner, tokens->name, GINT_TO_POINTER(tokens->token));
+ tokens++;
+ }
+
+ g_scanner_input_file(scanner, output);
+
+ bool found_begin = false, found_end = false;
+ while (!found_begin && !found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_BEGIN:
+ found_begin = true;
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (found_begin) {
+ unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
+ for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
+ if (page == NULL) {
+ continue;
+ }
+ g_object_set(zathura_page_get_widget(zathura, page), "search-results", NULL, NULL);
+ }
+ }
+
+ ret = false;
+ int page = -1, nextpage;
+ girara_list_t* hitlist = NULL;
+ zathura_rectangle_t* rectangle = NULL;
+
+ while (!found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_END:
+ found_end = true;
+ break;
+
+ case SYNCTEX_PROP_PAGE:
+ if (g_scanner_get_next_token(scanner) == G_TOKEN_INT) {
+ nextpage = g_scanner_cur_value(scanner).v_int;
+ if (page != nextpage) {
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+ hitlist = girara_list_new2((girara_free_function_t) g_free);
+ page = nextpage;
+ }
+ rectangle = g_malloc0(sizeof(zathura_rectangle_t));
+ girara_list_append(hitlist, rectangle);
+ }
+ break;
+
+ case SYNCTEX_PROP_H:
+ rectangle->x1 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_V:
+ rectangle->y2 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_WIDTH:
+ rectangle->x2 = rectangle->x1 + scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_HEIGHT:
+ rectangle->y1 = rectangle->y2 - scan_float(scanner);
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+
+ g_scanner_destroy(scanner);
+ close(output);
+ return ret;
+}
+
+bool
+cmd_synctex(girara_session_t* session, girara_list_t* argument_list)
+{
+ g_return_val_if_fail(session != NULL, false);
+ g_return_val_if_fail(session->global.data != NULL, false);
+ zathura_t* zathura = session->global.data;
+ if (zathura->document == NULL) {
+ girara_notify(session, GIRARA_ERROR, _("No document opened."));
+ return false;
+ }
+
+ const unsigned int argc = girara_list_size(argument_list);
+ if (argc != 1) {
+ girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
+ return false;
+ }
+
+ char* position = girara_list_nth(argument_list, 0);
+ return synctex_view(zathura, position);
+}
diff --git a/synctex.h b/synctex.h
index 1514117..373f519 100644
--- a/synctex.h
+++ b/synctex.h
@@ -6,5 +6,6 @@
#include "types.h"

void synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y);
+bool cmd_synctex(girara_session_t* session, girara_list_t* argument_list);

#endif
Sebastian Ramacher
2012-08-30 18:52:30 UTC
Permalink
First of all thanks for the patch and I'm sorry that I didn't have the time to
look at the patch yet.
Post by Roland Schatz
How do you connect that feature to a text editor?
My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
What about a dbus interface? Apparently there is a Glib binding for dbus. The
editor could send the messages to zathura via dbus-send.

Another possibility would be an XML-RPC interface but I didn't check if there
are any good and easy-to-use libraries for that.

Kind regards
Roland Schatz
2012-08-14 11:05:06 UTC
Permalink
I have attached the patch for forward synchronization.

This patch is a lot more complex than the previous one, and I haven't
really tested it rigorously (although I installed it on my production
system today).

It introduces a new command ":synctex <line>:<column>:<source file>"
(same syntax than the "synctex view -i" parameter). This command runs
synctex, parses the result and displays the result using the existing
"search-results" mechanism.

It introduces one new intltool string (the description of the :synctex
command), and reuses two others (the error messages wrong parameter
count and no document open). I haven't updated the translation yet.


The big open question:
How do you connect that feature to a text editor?

My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
-------------- next part --------------
diff --git a/config.c b/config.c
index 6db7ffc..5d7a956 100644
--- a/config.c
+++ b/config.c
@@ -9,6 +9,7 @@
#include "render.h"
#include "marks.h"
#include "utils.h"
+#include "synctex.h"

#include <girara/settings.h>
#include <girara/session.h>
@@ -308,6 +309,7 @@ config_load_default(zathura_t* zathura)
girara_inputbar_command_add(gsession, "nohlsearch", "nohl", cmd_nohlsearch, NULL, _("Don't highlight current search results"));
girara_inputbar_command_add(gsession, "hlsearch", NULL, cmd_hlsearch, NULL, _("Highlight current search results"));
girara_inputbar_command_add(gsession, "version", NULL, cmd_version, NULL, _("Show version information"));
+ girara_inputbar_command_add(gsession, "synctex", NULL, cmd_synctex, NULL, _("Synctex forward synchronization"));

girara_special_command_add(gsession, '/', cmd_search, true, FORWARD, NULL);
girara_special_command_add(gsession, '?', cmd_search, true, BACKWARD, NULL);
diff --git a/synctex.c b/synctex.c
index 9a9c91f..d4258fb 100644
--- a/synctex.c
+++ b/synctex.c
@@ -1,12 +1,16 @@
/* See LICENSE file for license and copyright information */

+#include <glib.h>
+#include <glib/gi18n.h>
+
#include "synctex.h"

#include "zathura.h"
#include "page.h"
#include "document.h"
+#include "utils.h"

-#include <glib.h>
+#include <girara/session.h>

void
synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)
@@ -29,3 +33,211 @@ synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)

g_free(buffer);
}
+
+static void
+synctex_record_hits(zathura_t* zathura, int page_idx, girara_list_t* hits, bool first)
+{
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_idx-1);
+ if (page == NULL)
+ return;
+
+ GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
+ g_object_set(page_widget, "draw-links", FALSE, NULL);
+ g_object_set(page_widget, "search-results", hits, NULL);
+
+ if (first) {
+ page_set_delayed(zathura, zathura_page_get_index(page));
+ g_object_set(page_widget, "search-current", 0, NULL);
+ }
+}
+
+enum {
+ SYNCTEX_RESULT_BEGIN = 1,
+ SYNCTEX_RESULT_END,
+ SYNCTEX_PROP_PAGE,
+ SYNCTEX_PROP_H,
+ SYNCTEX_PROP_V,
+ SYNCTEX_PROP_WIDTH,
+ SYNCTEX_PROP_HEIGHT,
+};
+
+static struct token {
+ const char* name;
+ guint token;
+} scanner_tokens[] = {
+ {"SyncTeX result begin", SYNCTEX_RESULT_BEGIN},
+ {"SyncTeX result end", SYNCTEX_RESULT_END},
+ {"Page:", SYNCTEX_PROP_PAGE},
+ {"h:", SYNCTEX_PROP_H},
+ {"v:", SYNCTEX_PROP_V},
+ {"W:", SYNCTEX_PROP_WIDTH},
+ {"H:", SYNCTEX_PROP_HEIGHT},
+ {NULL, 0}
+};
+
+static GScannerConfig scanner_config = {
+ .cset_skip_characters = "\n\r",
+ .cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z,
+ .cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z ": ",
+ .cpair_comment_single = NULL,
+ .case_sensitive = TRUE,
+ .scan_identifier = TRUE,
+ .scan_symbols = TRUE,
+ .scan_float = TRUE,
+ .numbers_2_int = TRUE,
+};
+
+static double
+scan_float(GScanner* scanner) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_FLOAT:
+ return g_scanner_cur_value(scanner).v_float;
+ case G_TOKEN_INT:
+ return g_scanner_cur_value(scanner).v_int;
+ default:
+ return 0.0;
+ }
+}
+
+static bool
+synctex_view(zathura_t* zathura, char* position)
+{
+ char* filename = g_strdup(zathura_document_get_path(zathura->document));
+ char* argv[] = {"synctex", "view", "-i", position, "-o", filename, NULL};
+ gint output;
+
+ bool ret = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &output, NULL, NULL);
+ g_free(filename);
+
+ if (!ret) {
+ return false;
+ }
+
+ GScanner* scanner = g_scanner_new(&scanner_config);
+ struct token* tokens = scanner_tokens;
+ while (tokens->name) {
+ g_scanner_add_symbol(scanner, tokens->name, GINT_TO_POINTER(tokens->token));
+ tokens++;
+ }
+
+ g_scanner_input_file(scanner, output);
+
+ bool found_begin = false, found_end = false;
+ while (!found_begin && !found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_BEGIN:
+ found_begin = true;
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (found_begin) {
+ unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
+ for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
+ if (page == NULL) {
+ continue;
+ }
+ g_object_set(zathura_page_get_widget(zathura, page), "search-results", NULL, NULL);
+ }
+ }
+
+ ret = false;
+ int page = -1, nextpage;
+ girara_list_t* hitlist = NULL;
+ zathura_rectangle_t* rectangle = NULL;
+
+ while (!found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_END:
+ found_end = true;
+ break;
+
+ case SYNCTEX_PROP_PAGE:
+ if (g_scanner_get_next_token(scanner) == G_TOKEN_INT) {
+ nextpage = g_scanner_cur_value(scanner).v_int;
+ if (page != nextpage) {
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+ hitlist = girara_list_new2((girara_free_function_t) g_free);
+ page = nextpage;
+ }
+ rectangle = g_malloc0(sizeof(zathura_rectangle_t));
+ girara_list_append(hitlist, rectangle);
+ }
+ break;
+
+ case SYNCTEX_PROP_H:
+ rectangle->x1 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_V:
+ rectangle->y2 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_WIDTH:
+ rectangle->x2 = rectangle->x1 + scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_HEIGHT:
+ rectangle->y1 = rectangle->y2 - scan_float(scanner);
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+
+ g_scanner_destroy(scanner);
+ close(output);
+ return ret;
+}
+
+bool
+cmd_synctex(girara_session_t* session, girara_list_t* argument_list)
+{
+ g_return_val_if_fail(session != NULL, false);
+ g_return_val_if_fail(session->global.data != NULL, false);
+ zathura_t* zathura = session->global.data;
+ if (zathura->document == NULL) {
+ girara_notify(session, GIRARA_ERROR, _("No document opened."));
+ return false;
+ }
+
+ const unsigned int argc = girara_list_size(argument_list);
+ if (argc != 1) {
+ girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
+ return false;
+ }
+
+ char* position = girara_list_nth(argument_list, 0);
+ return synctex_view(zathura, position);
+}
diff --git a/synctex.h b/synctex.h
index 1514117..373f519 100644
--- a/synctex.h
+++ b/synctex.h
@@ -6,5 +6,6 @@
#include "types.h"

void synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y);
+bool cmd_synctex(girara_session_t* session, girara_list_t* argument_list);

#endif
Sebastian Ramacher
2012-08-30 18:52:30 UTC
Permalink
First of all thanks for the patch and I'm sorry that I didn't have the time to
look at the patch yet.
Post by Roland Schatz
How do you connect that feature to a text editor?
My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
What about a dbus interface? Apparently there is a Glib binding for dbus. The
editor could send the messages to zathura via dbus-send.

Another possibility would be an XML-RPC interface but I didn't check if there
are any good and easy-to-use libraries for that.

Kind regards
Roland Schatz
2012-08-14 11:05:06 UTC
Permalink
I have attached the patch for forward synchronization.

This patch is a lot more complex than the previous one, and I haven't
really tested it rigorously (although I installed it on my production
system today).

It introduces a new command ":synctex <line>:<column>:<source file>"
(same syntax than the "synctex view -i" parameter). This command runs
synctex, parses the result and displays the result using the existing
"search-results" mechanism.

It introduces one new intltool string (the description of the :synctex
command), and reuses two others (the error messages wrong parameter
count and no document open). I haven't updated the translation yet.


The big open question:
How do you connect that feature to a text editor?

My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
-------------- next part --------------
diff --git a/config.c b/config.c
index 6db7ffc..5d7a956 100644
--- a/config.c
+++ b/config.c
@@ -9,6 +9,7 @@
#include "render.h"
#include "marks.h"
#include "utils.h"
+#include "synctex.h"

#include <girara/settings.h>
#include <girara/session.h>
@@ -308,6 +309,7 @@ config_load_default(zathura_t* zathura)
girara_inputbar_command_add(gsession, "nohlsearch", "nohl", cmd_nohlsearch, NULL, _("Don't highlight current search results"));
girara_inputbar_command_add(gsession, "hlsearch", NULL, cmd_hlsearch, NULL, _("Highlight current search results"));
girara_inputbar_command_add(gsession, "version", NULL, cmd_version, NULL, _("Show version information"));
+ girara_inputbar_command_add(gsession, "synctex", NULL, cmd_synctex, NULL, _("Synctex forward synchronization"));

girara_special_command_add(gsession, '/', cmd_search, true, FORWARD, NULL);
girara_special_command_add(gsession, '?', cmd_search, true, BACKWARD, NULL);
diff --git a/synctex.c b/synctex.c
index 9a9c91f..d4258fb 100644
--- a/synctex.c
+++ b/synctex.c
@@ -1,12 +1,16 @@
/* See LICENSE file for license and copyright information */

+#include <glib.h>
+#include <glib/gi18n.h>
+
#include "synctex.h"

#include "zathura.h"
#include "page.h"
#include "document.h"
+#include "utils.h"

-#include <glib.h>
+#include <girara/session.h>

void
synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)
@@ -29,3 +33,211 @@ synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y)

g_free(buffer);
}
+
+static void
+synctex_record_hits(zathura_t* zathura, int page_idx, girara_list_t* hits, bool first)
+{
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_idx-1);
+ if (page == NULL)
+ return;
+
+ GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
+ g_object_set(page_widget, "draw-links", FALSE, NULL);
+ g_object_set(page_widget, "search-results", hits, NULL);
+
+ if (first) {
+ page_set_delayed(zathura, zathura_page_get_index(page));
+ g_object_set(page_widget, "search-current", 0, NULL);
+ }
+}
+
+enum {
+ SYNCTEX_RESULT_BEGIN = 1,
+ SYNCTEX_RESULT_END,
+ SYNCTEX_PROP_PAGE,
+ SYNCTEX_PROP_H,
+ SYNCTEX_PROP_V,
+ SYNCTEX_PROP_WIDTH,
+ SYNCTEX_PROP_HEIGHT,
+};
+
+static struct token {
+ const char* name;
+ guint token;
+} scanner_tokens[] = {
+ {"SyncTeX result begin", SYNCTEX_RESULT_BEGIN},
+ {"SyncTeX result end", SYNCTEX_RESULT_END},
+ {"Page:", SYNCTEX_PROP_PAGE},
+ {"h:", SYNCTEX_PROP_H},
+ {"v:", SYNCTEX_PROP_V},
+ {"W:", SYNCTEX_PROP_WIDTH},
+ {"H:", SYNCTEX_PROP_HEIGHT},
+ {NULL, 0}
+};
+
+static GScannerConfig scanner_config = {
+ .cset_skip_characters = "\n\r",
+ .cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z,
+ .cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z ": ",
+ .cpair_comment_single = NULL,
+ .case_sensitive = TRUE,
+ .scan_identifier = TRUE,
+ .scan_symbols = TRUE,
+ .scan_float = TRUE,
+ .numbers_2_int = TRUE,
+};
+
+static double
+scan_float(GScanner* scanner) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_FLOAT:
+ return g_scanner_cur_value(scanner).v_float;
+ case G_TOKEN_INT:
+ return g_scanner_cur_value(scanner).v_int;
+ default:
+ return 0.0;
+ }
+}
+
+static bool
+synctex_view(zathura_t* zathura, char* position)
+{
+ char* filename = g_strdup(zathura_document_get_path(zathura->document));
+ char* argv[] = {"synctex", "view", "-i", position, "-o", filename, NULL};
+ gint output;
+
+ bool ret = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &output, NULL, NULL);
+ g_free(filename);
+
+ if (!ret) {
+ return false;
+ }
+
+ GScanner* scanner = g_scanner_new(&scanner_config);
+ struct token* tokens = scanner_tokens;
+ while (tokens->name) {
+ g_scanner_add_symbol(scanner, tokens->name, GINT_TO_POINTER(tokens->token));
+ tokens++;
+ }
+
+ g_scanner_input_file(scanner, output);
+
+ bool found_begin = false, found_end = false;
+ while (!found_begin && !found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_BEGIN:
+ found_begin = true;
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (found_begin) {
+ unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
+ for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
+ zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
+ if (page == NULL) {
+ continue;
+ }
+ g_object_set(zathura_page_get_widget(zathura, page), "search-results", NULL, NULL);
+ }
+ }
+
+ ret = false;
+ int page = -1, nextpage;
+ girara_list_t* hitlist = NULL;
+ zathura_rectangle_t* rectangle = NULL;
+
+ while (!found_end) {
+ switch (g_scanner_get_next_token(scanner)) {
+ case G_TOKEN_EOF:
+ found_end = true;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT(g_scanner_cur_value(scanner).v_identifier)) {
+ case SYNCTEX_RESULT_END:
+ found_end = true;
+ break;
+
+ case SYNCTEX_PROP_PAGE:
+ if (g_scanner_get_next_token(scanner) == G_TOKEN_INT) {
+ nextpage = g_scanner_cur_value(scanner).v_int;
+ if (page != nextpage) {
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+ hitlist = girara_list_new2((girara_free_function_t) g_free);
+ page = nextpage;
+ }
+ rectangle = g_malloc0(sizeof(zathura_rectangle_t));
+ girara_list_append(hitlist, rectangle);
+ }
+ break;
+
+ case SYNCTEX_PROP_H:
+ rectangle->x1 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_V:
+ rectangle->y2 = scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_WIDTH:
+ rectangle->x2 = rectangle->x1 + scan_float(scanner);
+ break;
+
+ case SYNCTEX_PROP_HEIGHT:
+ rectangle->y1 = rectangle->y2 - scan_float(scanner);
+ break;
+ }
+ break;
+
+ default:
+ /* skip everything else */
+ break;
+ }
+ }
+
+ if (hitlist) {
+ synctex_record_hits(zathura, page, hitlist, !ret);
+ ret = true;
+ }
+
+ g_scanner_destroy(scanner);
+ close(output);
+ return ret;
+}
+
+bool
+cmd_synctex(girara_session_t* session, girara_list_t* argument_list)
+{
+ g_return_val_if_fail(session != NULL, false);
+ g_return_val_if_fail(session->global.data != NULL, false);
+ zathura_t* zathura = session->global.data;
+ if (zathura->document == NULL) {
+ girara_notify(session, GIRARA_ERROR, _("No document opened."));
+ return false;
+ }
+
+ const unsigned int argc = girara_list_size(argument_list);
+ if (argc != 1) {
+ girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
+ return false;
+ }
+
+ char* position = girara_list_nth(argument_list, 0);
+ return synctex_view(zathura, position);
+}
diff --git a/synctex.h b/synctex.h
index 1514117..373f519 100644
--- a/synctex.h
+++ b/synctex.h
@@ -6,5 +6,6 @@
#include "types.h"

void synctex_edit(zathura_t* zathura, zathura_page_t* page, int x, int y);
+bool cmd_synctex(girara_session_t* session, girara_list_t* argument_list);

#endif
Sebastian Ramacher
2012-08-30 18:52:30 UTC
Permalink
First of all thanks for the patch and I'm sorry that I didn't have the time to
look at the patch yet.
Post by Roland Schatz
How do you connect that feature to a text editor?
My personal solution to this is a vim-script that uses xdotool to
simulate keypresses that issue the :synctex command. But that seems like
a big ugly hack. A better solution would be to somehow make it possible
to send remote commands to zathura, similar to "vim --remote". But I
have no idea how "vim --remote" is implemented, I have to do more
research. I'm not sure whether it is possible to implement that in a
platform independent way...
What about a dbus interface? Apparently there is a Glib binding for dbus. The
editor could send the messages to zathura via dbus-send.

Another possibility would be an XML-RPC interface but I didn't check if there
are any good and easy-to-use libraries for that.

Kind regards
Sebastian Ramacher
2013-12-24 16:57:54 UTC
Permalink
Post by Roland Schatz
I have attached the patch for forward synchronization.
Finally it's happening.

The major part of the patch has already been applied for some time but was not
usable at all. I've started to implement the remaining bits. The current
progress can be seen in the branch feature/synctex.

zathura has gained a new command line argument --syntex-forward. If zathura is
called as 'zathura --syntex-forward <input> <output>', zathura will search for a
running instance having the file <output> open and it will send <input> to the
that instance. <input> is of the same format as the argument passed to syntex
view -i. (Note that <output> currently needs to be an absolut path. I'll fix
that in the feature.)

I've also commited a first ftplugin for vim (tex_zathurasynctex.vim). With this
plugin enabled <leader>f will perform a forward sync. The detection of the
correct <output> probably needs some love (it currently tries a function
provided by latex-suite and some heuristic to determine <output> by checking the
available syntex data).

Any comments are very welcome (expecially regarding the vim plugin).

Regards
--
Sebastian Ramacher
Loading...