Roland Schatz
2012-08-14 11:05:06 UTC
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
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