[gnome-latex: 169/205] Output filter: extract info and show stats



commit 5e0911062318fe964ded9084b4d830bc97d558a1
Author: Sébastien Wilmet <sebastien wilmet gmail com>
Date:   Mon Jan 18 02:33:23 2010 +0100

    Output filter: extract info and show stats
    
    The output filter is now almost the same as in Kile. The output of the
    compilation is filtered so only usefull messages are showed. A message
    can be an error, a warning, a badbox or an other thing. Later, this
    messages will be displayed in different colors. Now, to distinguish the
    messages, a symbol between [ ] is showed ("[x]" for an error, "[+]" for
    a warning, etc).
    
    The informations extracted are the files that TeX is processing, the
    line and obviously the message.
    
    There are statistics about how many errors, warnings and badboxes
    occured during the compilation.

 TODO                      |  12 +-
 src/callbacks.c           |   3 +
 src/external_commands.c   |  36 +-
 src/latex_output_filter.c | 819 +++++++++++++++++++++++++++++++++++++++++++++-
 src/latex_output_filter.h |  39 +++
 src/main.c                |   4 +-
 6 files changed, 876 insertions(+), 37 deletions(-)
---
diff --git a/TODO b/TODO
index 66320b4..d989a9e 100644
--- a/TODO
+++ b/TODO
@@ -4,12 +4,18 @@ TODO LaTeXila
        x filter the messages
        x show the exit code
        x button to stop the execution
-       - show statistics: nb of errors, warnings and badboxes
-       - extract informations: file, line, message
+       x show statistics: nb of errors, warnings and badboxes
+       x extract informations: file, line, message
        - jump to lines and files: GtkTextBuffer -> GtkListStore
        - colors
        - go to the previous/next error/warning/badbox
 
 - Auto-completion of LaTeX commands
 
-- Change the title of the window with the filename of the current document
+- Some various improvements:
+       - Change the title of the window with the filename of the current document
+       - Make a copy of a file before saving it for the first time (with a name like "file~")
+       - Autosave files every X minutes
+       - Symbols: most recently used
+       - File browser: list with all parents directories
+       - Create personnal templates: choose an icon
diff --git a/src/callbacks.c b/src/callbacks.c
index ed345e9..2b653b7 100644
--- a/src/callbacks.c
+++ b/src/callbacks.c
@@ -39,6 +39,7 @@
 #include "external_commands.h"
 #include "prefs.h"
 #include "file_browser.h"
+#include "latex_output_filter.h"
 
 static void close_document (gint index);
 static void save_as_dialog (void);
@@ -1775,6 +1776,8 @@ free_latexila (void)
 
        for (int i = 0 ; i < 7 ; i++)
                g_object_unref (latexila.symbols.list_stores[i]);
+
+       latex_output_filter_free ();
 }
 
 static void
diff --git a/src/external_commands.c b/src/external_commands.c
index b56baaf..cb45887 100644
--- a/src/external_commands.c
+++ b/src/external_commands.c
@@ -316,7 +316,6 @@ start_command_with_output (gchar **command)
                        G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
                        (GSpawnChildSetupFunc) cb_spawn_setup, NULL,
                        &child_pid, NULL, &out, NULL, &error);
-       g_free (dir);
 
        // an error occured
        if (error != NULL)
@@ -326,9 +325,14 @@ start_command_with_output (gchar **command)
                print_log_add (latexila.action_log.text_view, command_output, TRUE);
                g_free (command_output);
                g_error_free (error);
+               g_free (dir);
                return;
        }
 
+       if (! show_all_output)
+               latex_output_filter_set_path (dir);
+       g_free (dir);
+
        output_status = OUTPUT_GO_FETCHING;
        exit_code_set = FALSE;
 
@@ -417,7 +421,11 @@ cb_watch_output_command (GIOChannel *channel, GIOCondition condition,
                                }
 
                                else
+                               {
+                                       // delete the \n
+                                       line_utf8[strlen (line_utf8) - 1] = '\0';
                                        latex_output_filter (line_utf8);
+                               }
 
                                g_free (line_utf8);
                        }
@@ -454,27 +462,6 @@ cb_watch_output_command (GIOChannel *channel, GIOCondition condition,
        return TRUE;
 }
 
-/*
-static void
-output_filter_line (const gchar *line)
-{
-       if (line == NULL || strlen (line) == 0)
-               return;
-
-       if (g_regex_match_simple ("[^:]+:[0-9]+:.*", line, 0, 0)
-                       || g_regex_match_simple ("lines? [0-9]+", line, 0, 0)
-                       || strstr (line, "LaTeX Error")
-                       || strstr (line, "Output written on")
-                       || strstr (line, "Warning")
-                       || strstr (line, "Overfull")
-                       || strstr (line, "Underfull"))
-       {
-               print_log_add (latexila.action_log.text_view, line, FALSE);
-               flush_queue ();
-       }
-}
-*/
-
 static void
 cb_child_watch (GPid pid, gint status, gpointer user_data)
 {
@@ -495,9 +482,12 @@ finish_execute (void)
        if (! exit_code_set || output_status != OUTPUT_STOP_REQUEST)
                return;
 
+       if (! show_all_output)
+               latex_output_filter_print_stats ();
+
        if (child_pid_exit_code > -1)
        {
-               gchar *exit_code = g_strdup_printf ("exit code: %d\n",
+               gchar *exit_code = g_strdup_printf ("exit code: %d",
                                child_pid_exit_code);
 
                if (child_pid_exit_code == 0)
diff --git a/src/latex_output_filter.c b/src/latex_output_filter.c
index 3d278a3..a329a6c 100644
--- a/src/latex_output_filter.c
+++ b/src/latex_output_filter.c
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <ctype.h> // for isspace()
 #include <locale.h>
 #include <libintl.h>
 #include <gtk/gtk.h>
@@ -28,22 +29,820 @@
 #include "main.h"
 #include "print.h"
 #include "utils.h"
+#include "latex_output_filter.h"
+
+static gboolean detect_badbox (const gchar *line);
+static gboolean detect_badbox_line (const gchar *badbox,
+               gboolean current_line_is_empty);
+
+static gboolean detect_warning (const gchar *line);
+static gboolean detect_warning_line (const gchar *warning,
+               gboolean current_line_is_empty);
+
+static gboolean detect_error (const gchar *line);
+static gboolean detect_other (const gchar *line);
+
+static void update_stack_file (const gchar *line);
+static void update_stack_file_heuristic (const gchar *line);
+
+static gboolean file_exists (const gchar *filename);
+static gchar * get_current_filename (void);
+static void push_file_on_stack (gchar *filename, gboolean reliable);
+static void pop_file_from_stack (void);
+static gboolean top_file_on_stack_reliable (void);
+static void print_msg (void);
+
+// the current message
+static message_t msg;
+
+// if a message is splitted, we enter in a different status, so we fetch the end
+// of the message
+static enum filter_status status = START;
+
+// if a message is splitted, the lines are concatenated in this buffer
+static gchar line_buf[BUFFER_SIZE] = "";
+static gint nb_lines = 0;
+
+// the stack containing the files that TeX is processing
+static GSList *stack_file = NULL;
+
+// the directory where the document is compiled
+static const gchar *path = NULL;
+
+// some regex, initialised in latex_output_filter_init() and freed by
+// latex_output_filter_free()
+static GRegex *reg_badbox = NULL;
+static GRegex *reg_badbox_lines = NULL;
+static GRegex *reg_badbox_line = NULL;
+static GRegex *reg_badbox_output = NULL;
+
+static GRegex *reg_warning = NULL;
+static GRegex *reg_warning_no_file = NULL;
+static GRegex *reg_warning_line = NULL;
+static GRegex *reg_warning_international_line = NULL;
+
+static GRegex *reg_latex_error = NULL;
+static GRegex *reg_pdflatex_error = NULL;
+static GRegex *reg_tex_error = NULL;
+static GRegex *reg_error_line = NULL;
+
+static GRegex *reg_file_pop = NULL;
+
+// for statistics
+static gint nb_badboxes = 0;
+static gint nb_warnings = 0;
+static gint nb_errors = 0;
 
 void
 latex_output_filter (const gchar *line)
 {
-       if (line == NULL || strlen (line) == 0)
+       if (line == NULL)
                return;
 
-       if (g_regex_match_simple ("[^:]+:[0-9]+:.*", line, 0, 0)
-                       || g_regex_match_simple ("lines? [0-9]+", line, 0, 0)
-                       || strstr (line, "LaTeX Error")
-                       || strstr (line, "Output written on")
-                       || strstr (line, "Warning")
-                       || strstr (line, "Overfull")
-                       || strstr (line, "Underfull"))
+       switch (status)
+       {
+               case START:
+                       if (strlen (line) == 0)
+                               return;
+
+                       if (! (detect_badbox (line) || detect_warning (line)
+                                               || detect_error (line) || detect_other (line)))
+                               update_stack_file (line);
+                       break;
+
+               case BADBOX:
+                       detect_badbox (line);
+                       break;
+
+               case WARNING:
+                       detect_warning (line);
+                       break;
+
+               case ERROR:
+               case ERROR_SEARCH_LINE:
+                       detect_error (line);
+                       break;
+
+               case FILENAME:
+               case FILENAME_HEURISTIC:
+                       update_stack_file (line);
+
+               default:
+                       status = START;
+                       break;
+       }
+}
+
+void
+latex_output_filter_init (void)
+{
+       reg_badbox = g_regex_new ("^(Over|Under)full \\\\[hv]box", 0, 0, NULL);
+       reg_badbox_lines = g_regex_new ("(.*) at lines ([0-9]+)--([0-9]+)", 0, 0, NULL);
+       reg_badbox_line = g_regex_new ("(.*) at line ([0-9]+)", 0, 0, NULL);
+       reg_badbox_output = g_regex_new ("(.*)has occurred while \\output is active", 0, 0, NULL);
+
+       reg_warning = g_regex_new ("^(((! )?(La|pdf)TeX)|Package|Class) .*Warning.*:(.*)",
+                       G_REGEX_CASELESS, 0, NULL);
+       reg_warning_no_file = g_regex_new ("(No file .*)", 0, 0, NULL);
+       reg_warning_line = g_regex_new ("(.*) on input line ([0-9]+)\\.$", 0, 0, NULL);
+       reg_warning_international_line = g_regex_new ("(.*)([0-9]+)\\.$", 0, 0, NULL);
+
+       reg_latex_error = g_regex_new ("^! LaTeX Error: (.*)$", 0, 0, NULL);
+       reg_pdflatex_error = g_regex_new ("^Error: pdflatex (.*)$", 0, 0, NULL);
+       reg_tex_error = g_regex_new ("^! (.*)\\.$", 0, 0, NULL);
+       reg_error_line = g_regex_new ("^l\\.([0-9]+)(.*)", 0, 0, NULL);
+
+       reg_file_pop = g_regex_new ("(\\) )?:<-$", 0, 0, NULL);
+
+       msg.line = NO_LINE;
+}
+
+void
+latex_output_filter_free (void)
+{
+       g_regex_unref (reg_badbox);
+       g_regex_unref (reg_badbox_lines);
+       g_regex_unref (reg_badbox_line);
+       g_regex_unref (reg_badbox_output);
+       g_regex_unref (reg_warning);
+       g_regex_unref (reg_warning_no_file);
+       g_regex_unref (reg_warning_line);
+       g_regex_unref (reg_warning_international_line);
+       g_regex_unref (reg_latex_error);
+       g_regex_unref (reg_pdflatex_error);
+       g_regex_unref (reg_tex_error);
+       g_regex_unref (reg_error_line);
+       g_regex_unref (reg_file_pop);
+       g_free ((gpointer) path);
+}
+
+void
+latex_output_filter_set_path (const gchar *dir)
+{
+       path = g_strdup (dir);
+}
+
+void
+latex_output_filter_print_stats (void)
+{
+       gchar *str;
+       if (nb_errors > 1)
+               str = g_strdup_printf (_("%d errors, "), nb_errors);
+       else
+               str = g_strdup_printf (_("%d error, "), nb_errors);
+       if (nb_warnings > 1)
+       {
+               gchar *tmp = g_strdup_printf (_("%s%d warnings, "), str, nb_warnings);
+               g_free (str);
+               str = tmp;
+       }
+       else
+       {
+               gchar *tmp = g_strdup_printf (_("%s%d warning, "), str, nb_warnings);
+               g_free (str);
+               str = tmp;
+       }
+       if (nb_badboxes > 1)
+       {
+               gchar *tmp = g_strdup_printf (_("%s%d badboxes\n"), str, nb_badboxes);
+               g_free (str);
+               str = tmp;
+       }
+       else
+       {
+               gchar *tmp = g_strdup_printf (_("%s%d badbox\n"), str, nb_badboxes);
+               g_free (str);
+               str = tmp;
+       }
+
+       print_log_add (latexila.action_log.text_view, str, FALSE);
+       flush_queue ();
+       g_free (str);
+
+       nb_badboxes = 0;
+       nb_warnings = 0;
+       nb_errors = 0;
+
+       // if all the files in the stack are not popped
+       gint nb_files_in_stack = g_slist_length (stack_file);
+       for (int i = 0 ; i < nb_files_in_stack ; i++)
+               pop_file_from_stack ();
+}
+
+static gboolean
+detect_badbox (const gchar *line)
+{
+       gboolean current_line_is_empty;
+
+       switch (status)
+       {
+               case START:
+                       if (g_regex_match (reg_badbox, line, 0, NULL))
+                       {
+                               msg.message_type = TYPE_BADBOX;
+                               nb_badboxes++;
+
+                               if (detect_badbox_line (line, FALSE))
+                                       print_msg ();
+                               else
+                               {
+                                       g_strlcpy (line_buf, line, BUFFER_SIZE);
+                                       nb_lines++;
+                               }
+
+                               return TRUE;
+                       }
+                       return FALSE;
+                       break;
+               
+               case BADBOX:
+                       g_strlcat (line_buf, line, BUFFER_SIZE);
+                       nb_lines++;
+                       current_line_is_empty = strlen (line) == 0;
+                       if (detect_badbox_line (line_buf, current_line_is_empty))
+                       {
+                               print_msg ();
+                               nb_lines = 0;
+                       }
+
+                       // the return value is not important here
+                       return TRUE;
+                       break;
+
+               default:
+                       break;
+       }
+
+       return FALSE;
+}
+
+static gboolean
+detect_badbox_line (const gchar *badbox, gboolean current_line_is_empty)
+{
+       if (g_regex_match (reg_badbox_lines, badbox, 0, NULL))
+       {
+               status = START;
+               gchar **strings = g_regex_split (reg_badbox_lines, badbox, 0);
+               msg.message = g_strdup (strings[1]);
+               long n1 = strtol (strings[2], NULL, 10);
+               long n2 = strtol (strings[3], NULL, 10);
+               msg.line = n1 < n2 ? n1 : n2;
+               g_strfreev (strings);
+               return TRUE;
+       }
+
+       else if (g_regex_match (reg_badbox_line, badbox, 0, NULL))
+       {
+               status = START;
+               gchar **strings = g_regex_split (reg_badbox_line, badbox, 0);
+               msg.message = g_strdup (strings[1]);
+               msg.line = strtol (strings[2], NULL, 10);
+               g_strfreev (strings);
+               return TRUE;
+       }
+
+       else if (g_regex_match (reg_badbox_output, badbox, 0, NULL))
+       {
+               status = START;
+               gchar **strings = g_regex_split (reg_badbox_output, badbox, 0);
+               msg.message = g_strdup (strings[1]);
+               msg.line = NO_LINE;
+               g_strfreev (strings);
+               return TRUE;
+       }
+
+       else if (nb_lines > 4 || current_line_is_empty)
+       {
+               status = START;
+               msg.message = g_strdup (badbox);
+               msg.line = NO_LINE;
+               return TRUE;
+       }
+
+       status = BADBOX;
+       return FALSE;
+}
+
+static gboolean
+detect_warning (const gchar *line)
+{
+       gboolean current_line_is_empty;
+       gchar **strings;
+
+       switch (status)
+       {
+               case START:
+                       if (g_regex_match (reg_warning, line, 0, NULL))
+                       {
+                               nb_warnings++;
+                               msg.message_type = TYPE_WARNING;
+
+                               strings = g_regex_split (reg_warning, line, 0);
+
+                               if (detect_warning_line (strings[5], FALSE))
+                                       print_msg ();
+                               else
+                               {
+                                       g_strlcpy (line_buf, strings[5], BUFFER_SIZE);
+                                       nb_lines++;
+                               }
+
+                               g_strfreev (strings);
+                               return TRUE;
+                       }
+
+                       else if (g_regex_match (reg_warning_no_file, line, 0, NULL))
+                       {
+                               nb_warnings++;
+                               msg.message_type = TYPE_WARNING;
+                               strings = g_regex_split (reg_warning_no_file, line, 0);
+                               msg.message = g_strdup (strings[1]);
+                               g_strfreev (strings);
+                               msg.line = NO_LINE;
+                               print_msg ();
+                               return TRUE;
+                       }
+
+                       return FALSE;
+                       break;
+
+               case WARNING:
+                       g_strlcat (line_buf, line, BUFFER_SIZE);
+                       nb_lines++;
+                       current_line_is_empty = strlen (line) == 0;
+                       if (detect_warning_line (line_buf, current_line_is_empty))
+                       {
+                               print_msg ();
+                               nb_lines = 0;
+                       }
+
+                       // the return value is not important here
+                       return TRUE;
+                       break;
+
+               default:
+                       break;
+       }
+
+       return FALSE;
+}
+
+static gboolean
+detect_warning_line (const gchar *warning, gboolean current_line_is_empty)
+{
+       if (g_regex_match (reg_warning_line, warning, 0, NULL))
+       {
+               status = START;
+               gchar **strings = g_regex_split (reg_warning_line, warning, 0);
+               msg.message = g_strdup (strings[1]);
+               msg.line = strtol (strings[2], NULL, 10);
+               g_strfreev (strings);
+               return TRUE;
+       }
+       
+       else if (g_regex_match (reg_warning_international_line, warning, 0, NULL))
        {
-               print_log_add (latexila.action_log.text_view, line, FALSE);
-               flush_queue ();
+               status = START;
+               gchar **strings = g_regex_split (reg_warning_international_line, warning, 0);
+               msg.message = g_strdup (strings[1]);
+               msg.line = strtol (strings[2], NULL, 10);
+               g_strfreev (strings);
+               return TRUE;
+       }
+
+       else if (warning[strlen (warning) - 1] == '.')
+       {
+               status = START;
+               msg.message = g_strdup (warning);
+               msg.line = NO_LINE;
+               return TRUE;
+       }
+
+       else if (nb_lines > 5 || current_line_is_empty)
+       {
+               status = START;
+               msg.message = g_strdup (warning);
+               msg.line = NO_LINE;
+               return TRUE;
+       }
+
+       status = WARNING;
+       return FALSE;
+}
+
+static gboolean
+detect_error (const gchar *line)
+{
+       gboolean found = FALSE;
+       gchar *tmp;
+       gchar **strings;
+
+       switch (status)
+       {
+               case START:
+                       if (g_regex_match (reg_latex_error, line, 0, NULL))
+                       {
+                               found = TRUE;
+                               strings = g_regex_split (reg_latex_error, line, 0);
+                               tmp = g_strdup (strings[1]);
+                               g_strfreev (strings);
+                       }
+
+                       else if (g_regex_match (reg_pdflatex_error, line, 0, NULL))
+                       {
+                               found = TRUE;
+                               strings = g_regex_split (reg_pdflatex_error, line, 0);
+                               tmp = g_strdup (strings[1]);
+                               g_strfreev (strings);
+                       }
+
+                       else if (g_regex_match (reg_tex_error, line, 0, NULL))
+                       {
+                               found = TRUE;
+                               strings = g_regex_split (reg_tex_error, line, 0);
+                               tmp = g_strdup (strings[1]);
+                               g_strfreev (strings);
+                       }
+
+                       if (found)
+                       {
+                               nb_errors++;
+                               nb_lines++;
+                               msg.message_type = TYPE_ERROR;
+
+                               // the message is complete
+                               if (line[strlen (line) - 1] == '.')
+                               {
+                                       msg.message = tmp;
+                                       status = ERROR_SEARCH_LINE;
+                               }
+                               // the message is splitted
+                               else
+                               {
+                                       g_strlcpy (line_buf, tmp, BUFFER_SIZE);
+                                       g_free (tmp);
+                                       status = ERROR;
+                               }
+                               return TRUE;
+                       }
+
+                       return FALSE;
+                       break;
+
+               case ERROR:
+                       g_strlcat (line_buf, line, BUFFER_SIZE);
+                       nb_lines++;
+
+                       if (line[strlen (line) - 1] == '.')
+                       {
+                               msg.message = g_strdup (line_buf);
+                               status = ERROR_SEARCH_LINE;
+                       }
+                       else if (nb_lines > 4)
+                       {
+                               msg.message = g_strdup (line_buf);
+                               msg.line = NO_LINE;
+                               print_msg ();
+                               nb_lines = 0;
+                               status = START;
+                       }
+                       
+                       // the return value is not important here
+                       return TRUE;
+                       break;
+
+               case ERROR_SEARCH_LINE:
+                       nb_lines++;
+                       if (g_regex_match (reg_error_line, line, 0, NULL))
+                       {
+                               strings = g_regex_split (reg_error_line, line, 0);
+                               msg.line = strtol (strings[1], NULL, 10);
+                               g_strfreev (strings);
+                               print_msg ();
+                               nb_lines = 0;
+                               status = START;
+                               return TRUE;
+                       }
+                       else if (nb_lines > 11)
+                       {
+                               msg.line = NO_LINE;
+                               print_msg ();
+                               nb_lines = 0;
+                               status = START;
+                               return TRUE;
+                       }
+                       break;
+
+               default:
+                       break;
        }
+
+       return FALSE;
+}
+
+static gboolean
+detect_other (const gchar *line)
+{
+       if (strstr (line, "Output written on"))
+       {
+               msg.message = g_strdup (line);
+               msg.line = NO_LINE;
+               msg.message_type = TYPE_OTHER;
+               print_msg ();
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+// There are basically two ways to detect the current file TeX is processing:
+//      1) Use \Input (srctex or srcltx package) and \include exclusively. This will
+//      cause (La)TeX to print the line ":<+ filename"  in the log file when opening a file, 
+//      ":<-" when closing a file. Filenames pushed on the stack in this mode are marked
+//      as reliable.
+//
+//      2) Since people will probably also use the \input command, we also have to be
+//      to detect the old-fashioned way. TeX prints '(filename' when opening a file and a ')'
+//      when closing one. It is impossible to detect this with 100% certainty (TeX prints many messages
+//      and even text (a context) from the TeX source file, there could be unbalanced parentheses), 
+//      so we use a heuristic algorithm. In heuristic mode a ')' will only be considered as a signal that 
+//      TeX is closing a file if the top of the stack is not marked as "reliable". 
+//      
+// The method used here is almost the same as in Kile.
+static void
+update_stack_file (const gchar *line)
+{
+       static gchar filename[BUFFER_SIZE] = "";
+       gchar *tmp;
+
+       switch (status)
+       {
+               case START:
+               case FILENAME_HEURISTIC:
+                       // TeX is opening a file
+                       if (g_str_has_prefix (line, ":<+ "))
+                       {
+                               g_strlcpy (filename, line + 4, BUFFER_SIZE);
+                               g_strstrip (filename);
+                               status = FILENAME;
+                       }
+                       // TeX closed a file
+                       else if (g_regex_match (reg_file_pop, line, 0, NULL)
+                                       || g_str_has_prefix (line, ":<-"))
+                               pop_file_from_stack ();
+                       // fallback to the heuristic detection of filenames
+                       else
+                               update_stack_file_heuristic (line);
+                       break;
+
+               case FILENAME:
+                       // The partial filename was followed by '(', this means that TeX is
+                       // signalling it is opening the file. We are sure the filename is
+                       // complete now. Don't call update_stack_file_heuristic()
+                       // since we don't want the filename on the stack twice.
+                       if (line[0] == '(' || g_str_has_prefix (line, "\\openout"))
+                       {
+                               push_file_on_stack (filename, TRUE);
+                               status = START;
+                       }
+
+                       // The partial filename was followed by a TeX error, meaning the
+                       // file doesn't exist. Don't push it on the stack, instead try to
+                       // detect the error.
+                       else if (line[0] == '!')
+                       {
+                               status = START;
+                               detect_error (line);
+                       }
+                       else if (g_str_has_prefix (line, "No file"))
+                       {
+                               status = START;
+                               detect_warning (line);
+                       }
+
+                       // the filename is not complete
+                       else
+                       {
+                               tmp = g_strdup (line);
+                               g_strstrip (tmp);
+                               g_strlcat (filename, tmp, BUFFER_SIZE);
+                               g_free (tmp);
+                       }
+                       break;
+
+               default:
+                       break;
+       }
+}
+
+static void
+update_stack_file_heuristic (const gchar *line)
+{
+       static gchar filename[BUFFER_SIZE] = "";
+       gboolean expect_filename = status == FILENAME_HEURISTIC;
+       gint index = 0;
+       int length = strlen (line);
+
+       // handle special case
+       if (expect_filename && length > 0 && line[0] == ')')
+       {
+               push_file_on_stack (filename, FALSE);
+               expect_filename = FALSE;
+               status = START;
+       }
+
+       // scan for parentheses and grab filenames
+       for (int i = 0 ; i < length ; i++)
+       {
+               /*
+               We're expecting a filename. If a filename really ends at this position,
+               one of the following must be true:
+                       1) Next character is a space, indicating the end of a filename
+                         (yes, there can't spaces in the path, this is a TeX limitation).
+                       2) We're at the end of the line, the filename is probably
+                          continued on the next line.
+                       3) The file was closed already, signalled by the ')'.
+               */
+
+               gboolean is_last_char = i + 1 == length;
+               gboolean next_is_terminator =
+                       is_last_char ? FALSE : (isspace (line[i+1]) || line[i+1] == ')');
+
+               if (expect_filename && (is_last_char || next_is_terminator))
+               {
+                       g_strlcat (filename, line + index, strlen (filename) + i - index + 2);
+
+                       if (strlen (filename) == 0)
+                               continue;
+
+                       // by default, an output line is 79 characters max
+                       if ((is_last_char && i < 78) || next_is_terminator || file_exists (filename))
+                       {
+                               push_file_on_stack (filename, FALSE);
+                               expect_filename = FALSE;
+                               status = START;
+                       }
+
+                       // Guess the filename is continued on the next line, only if the
+                       // current filename does not exist
+                       else if (is_last_char)
+                       {
+                               if (file_exists (filename))
+                               {
+                                       push_file_on_stack (filename, FALSE);
+                                       expect_filename = FALSE;
+                                       status = START;
+                               }
+                               else
+                                       status = FILENAME_HEURISTIC;
+                       }
+
+                       // filename not detected
+                       else
+                       {
+                               status = START;
+                               filename[0] = '\0';
+                               expect_filename = FALSE;
+                       }
+               }
+
+               // TeX is opening a file
+               else if (line[i] == '(')
+               {
+                       status = START;
+                       filename[0] = '\0';
+                       // we need to extract the filename
+                       expect_filename = TRUE;
+                       // this is where the filename is supposed to start
+                       index = i + 1;
+               }
+
+               // TeX is closing a file
+               // If this filename was pushed on the stack by the reliable ":<+-"
+               // method, don't pop, a ":<-" will follow. This helps in preventing
+               // unbalanced ')' from popping filenames from the stack too soon.
+               else if (line[i] == ')' && g_slist_length (stack_file) > 0
+                               && ! top_file_on_stack_reliable ())
+                       pop_file_from_stack ();
+       }
+}
+
+static gboolean
+file_exists (const gchar *filename)
+{
+       if (g_path_is_absolute (filename))
+               return g_file_test (filename, G_FILE_TEST_IS_REGULAR);
+
+       gchar *full_path = g_build_filename (path, filename, NULL);
+       if (g_file_test (full_path, G_FILE_TEST_IS_REGULAR))
+       {
+               g_free (full_path);
+               return TRUE;
+       }
+
+       // try to add various extensions on the filename to see if the file exists
+       gchar *extensions[] = {".tex", ".ltx", ".latex", ".dtx", ".ins"};
+       guint nb_ext = G_N_ELEMENTS (extensions);
+       for (guint i = 0 ; i < nb_ext ; i++)
+       {
+               gchar *tmp = g_strdup_printf ("%s%s", full_path, extensions[i]);
+               if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR))
+               {
+                       g_free (tmp);
+                       g_free (full_path);
+                       return TRUE;
+               }
+               g_free (tmp);
+       }
+
+       return FALSE;
+}
+
+static gchar *
+get_current_filename (void)
+{
+       if (g_slist_length (stack_file) > 0)
+       {
+               file_in_stack_t *file = g_slist_nth_data (stack_file, 0);
+               return file->filename;
+       }
+
+       return NULL;
+}
+
+static void
+push_file_on_stack (gchar *filename, gboolean reliable)
+{
+       //print_info ("***\tpush\t\"%s\" (%s)", filename, reliable ? "reliable" : "not reliable");
+       file_in_stack_t *file = g_malloc (sizeof (file_in_stack_t));
+       file->filename = g_strdup (filename);
+       file->reliable = reliable;
+       stack_file = g_slist_prepend (stack_file, file);
+}
+
+static void
+pop_file_from_stack (void)
+{
+       file_in_stack_t *file = g_slist_nth_data (stack_file, 0);
+       //print_info ("***\tpop\t\"%s\" (%s)", file->filename, file->reliable ? "reliable" : "not reliable");
+       stack_file = g_slist_remove (stack_file, file);
+       g_free (file->filename);
+       g_free (file);
+}
+
+static gboolean
+top_file_on_stack_reliable (void)
+{
+       // stack_file must contain at least one file
+       file_in_stack_t *file = g_slist_nth_data (stack_file, 0);
+       return file->reliable;
+}
+
+static void
+print_msg (void)
+{
+       gchar *str;
+       switch (msg.message_type)
+       {
+               case TYPE_ERROR:
+                       str = g_strdup ("[x] ");
+                       break;
+               case TYPE_WARNING:
+                       str = g_strdup ("[+] ");
+                       break;
+               case TYPE_BADBOX:
+                       str = g_strdup ("[-] ");
+                       break;
+               case TYPE_OTHER:
+                       str = g_strdup ("[ ] ");
+                       break;
+               default:
+                       break;
+       }
+
+       gchar *filename = get_current_filename ();
+       if (filename != NULL)
+       {
+               gchar *tmp = g_strdup_printf ("%s%s:", str, filename);
+               g_free (str);
+               str = tmp;
+       }
+
+       if (msg.line != NO_LINE)
+       {
+               gchar *tmp = g_strdup_printf ("%s%d:", str, msg.line);
+               g_free (str);
+               str = tmp;
+       }
+
+       gchar *tmp = g_strdup_printf ("%s%s\n", str, msg.message);
+       g_free (str);
+       str = tmp;
+
+       print_log_add (latexila.action_log.text_view, str, msg.message_type == TYPE_ERROR);
+       flush_queue ();
+       
+       msg.line = NO_LINE;
+       msg.message_type = TYPE_OTHER;
+       g_free (msg.message);
 }
diff --git a/src/latex_output_filter.h b/src/latex_output_filter.h
index 797b47c..c50af59 100644
--- a/src/latex_output_filter.h
+++ b/src/latex_output_filter.h
@@ -21,5 +21,44 @@
 #define LATEX_OUTPUT_FILTER_H
 
 void latex_output_filter (const gchar *line);
+void latex_output_filter_init (void);
+void latex_output_filter_free (void);
+void latex_output_filter_set_path (const gchar *dir);
+void latex_output_filter_print_stats (void);
+
+enum filter_status
+{
+       START,
+       BADBOX,
+       WARNING,
+       ERROR,
+       ERROR_SEARCH_LINE,
+       FILENAME,
+       FILENAME_HEURISTIC
+};
+
+typedef struct
+{
+       gchar *filename;
+       gboolean reliable;
+} file_in_stack_t;
+
+enum message_type
+{
+       TYPE_BADBOX,
+       TYPE_WARNING,
+       TYPE_ERROR,
+       TYPE_OTHER
+};
+
+typedef struct
+{
+       enum message_type message_type;
+       gchar *message;
+       gint line;
+} message_t;
+
+#define BUFFER_SIZE 1024
+#define NO_LINE -1
 
 #endif /* LATEX_OUTPUT_FILTER_H */
diff --git a/src/main.c b/src/main.c
index 78f836d..6b5dd46 100644
--- a/src/main.c
+++ b/src/main.c
@@ -35,6 +35,7 @@
 #include "prefs.h"
 #include "ui.h"
 #include "templates.h"
+#include "latex_output_filter.h"
 
 static gboolean option_version (const gchar *option_name, const gchar *value,
                gpointer data, GError **error);
@@ -561,8 +562,9 @@ main (int argc, char *argv[])
        if (option_new_document)
                cb_new ();
 
-       /* templates */
+       /* init various things */
        init_templates ();
+       latex_output_filter_init ();
 
 
        gtk_main ();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]