[latexila/wip/latexila-next] LatexilaPostProcessorLatex (not finished)



commit 16dfcd188b87ce3131b688d8d3efabe667b66d3c
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Sat Jul 12 13:40:12 2014 +0200

    LatexilaPostProcessorLatex (not finished)

 src/liblatexila/Makefile.am                     |    2 +
 src/liblatexila/latexila-build-job.c            |    1 +
 src/liblatexila/latexila-build-view.c           |   26 +-
 src/liblatexila/latexila-build-view.h           |    2 +
 src/liblatexila/latexila-post-processor-latex.c | 1220 +++++++++++++++++++++++
 src/liblatexila/latexila-post-processor-latex.h |   55 +
 src/liblatexila/latexila-post-processor.c       |   22 +
 src/liblatexila/latexila-post-processor.h       |   16 +
 src/liblatexila/latexila-types.h                |    1 +
 src/liblatexila/latexila.h                      |    1 +
 10 files changed, 1345 insertions(+), 1 deletions(-)
---
diff --git a/src/liblatexila/Makefile.am b/src/liblatexila/Makefile.am
index 1beb3cc..e2e33fa 100644
--- a/src/liblatexila/Makefile.am
+++ b/src/liblatexila/Makefile.am
@@ -20,6 +20,7 @@ liblatexila_headers =                         \
        latexila-build-view.h                   \
        latexila-post-processor.h               \
        latexila-post-processor-all-output.h    \
+       latexila-post-processor-latex.h         \
        latexila-synctex.h                      \
        latexila-types.h                        \
        latexila-utils.h
@@ -33,6 +34,7 @@ liblatexila_sources =                         \
        latexila-build-view.c                   \
        latexila-post-processor.c               \
        latexila-post-processor-all-output.c    \
+       latexila-post-processor-latex.c         \
        latexila-synctex.c                      \
        latexila-utils.c
 
diff --git a/src/liblatexila/latexila-build-job.c b/src/liblatexila/latexila-build-job.c
index 5b176b3..d32d70f 100644
--- a/src/liblatexila/latexila-build-job.c
+++ b/src/liblatexila/latexila-build-job.c
@@ -481,6 +481,7 @@ launch_subprocess (LatexilaBuildJob *build_job)
       build_job->priv->post_processor = latexila_post_processor_all_output_new ();
 
       latexila_post_processor_process_async (build_job->priv->post_processor,
+                                             build_job->priv->file,
                                              g_subprocess_get_stdout_pipe (subprocess),
                                              g_task_get_cancellable (build_job->priv->task),
                                              (GAsyncReadyCallback) post_processor_cb,
diff --git a/src/liblatexila/latexila-build-view.c b/src/liblatexila/latexila-build-view.c
index 1ee5260..af6680e 100644
--- a/src/liblatexila/latexila-build-view.c
+++ b/src/liblatexila/latexila-build-view.c
@@ -27,6 +27,7 @@
  */
 
 #include "latexila-build-view.h"
+#include <string.h>
 #include "latexila-utils.h"
 #include "latexila-enum-types.h"
 
@@ -77,6 +78,27 @@ G_DEFINE_TYPE_WITH_PRIVATE (LatexilaBuildView, latexila_build_view, GTK_TYPE_TRE
 static guint signals[LAST_SIGNAL];
 
 /**
+ * latexila_build_msg_reinit:
+ * @build_msg: a #LatexilaBuildMsg.
+ *
+ * Reinitializes a #LatexilaBuildMsg.
+ */
+void
+latexila_build_msg_reinit (LatexilaBuildMsg *build_msg)
+{
+  g_assert (build_msg != NULL);
+
+  g_free (build_msg->text);
+  g_free (build_msg->filename);
+
+  memset (build_msg, 0, sizeof (LatexilaBuildMsg));
+
+  build_msg->start_line = -1;
+  build_msg->end_line = -1;
+  build_msg->expand = TRUE;
+}
+
+/**
  * latexila_build_msg_new: (skip)
  *
  * Free the return value with latexila_build_msg_free() when no longer needed.
@@ -86,7 +108,9 @@ static guint signals[LAST_SIGNAL];
 LatexilaBuildMsg *
 latexila_build_msg_new (void)
 {
-  LatexilaBuildMsg *build_msg = g_slice_new0 (LatexilaBuildMsg);
+  LatexilaBuildMsg *build_msg;
+
+  build_msg = g_slice_new0 (LatexilaBuildMsg);
 
   build_msg->start_line = -1;
   build_msg->end_line = -1;
diff --git a/src/liblatexila/latexila-build-view.h b/src/liblatexila/latexila-build-view.h
index bc4245e..b99a962 100644
--- a/src/liblatexila/latexila-build-view.h
+++ b/src/liblatexila/latexila-build-view.h
@@ -113,6 +113,8 @@ struct _LatexilaBuildViewClass
 
 LatexilaBuildMsg *    latexila_build_msg_new                        (void);
 
+void                  latexila_build_msg_reinit                     (LatexilaBuildMsg *build_msg);
+
 void                  latexila_build_msg_free                       (LatexilaBuildMsg *build_msg);
 
 GType                 latexila_build_view_get_type                  (void) G_GNUC_CONST;
diff --git a/src/liblatexila/latexila-post-processor-latex.c b/src/liblatexila/latexila-post-processor-latex.c
new file mode 100644
index 0000000..7bf1d49
--- /dev/null
+++ b/src/liblatexila/latexila-post-processor-latex.c
@@ -0,0 +1,1220 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LaTeXila is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "latexila-post-processor-latex.h"
+#include <stdlib.h>
+#include <string.h>
+#include "latexila-build-view.h"
+
+#define NO_LINE (-1)
+
+/* If a message is split into several lines, we enter in a different state to
+ * fetch the end of the message.
+ */
+typedef enum
+{
+  STATE_START,
+  STATE_BADBOX,
+  STATE_WARNING,
+  STATE_ERROR,
+  STATE_ERROR_SEARCH_LINE,
+  STATE_FILENAME,
+  STATE_FILENAME_HEURISTIC
+} State;
+
+/* Opened file. They are present in a stack. It is used to know on which file an
+ * error or warning occurred.
+ */
+typedef struct
+{
+  gchar *filename;
+  guint reliable : 1;
+
+  /* Non-existent files are also pushed on the stack, because the corresponding
+   * ')' will pop it. If we don't push them, wrong files are popped.
+   * When a new message is added, the last _existing_ file is taken.
+   */
+  guint exists : 1;
+} OpenedFile;
+
+struct _LatexilaPostProcessorLatexPrivate
+{
+  GNode *messages;
+
+  /* The last message is used to insert in O(1) at the end of 'messages'. */
+  GNode *last_message;
+
+  /* Current message. */
+  LatexilaBuildMsg *cur_msg;
+
+  State state;
+
+  /* If a message is split into several lines, the lines are concatenated in
+   * line_buffer.
+   */
+  GString *line_buffer;
+  gint nb_lines;
+
+  /* If a filename is split into several lines. */
+  GString *filename_buffer;
+
+  /* The stack containing the files that TeX is processing. The top of the stack
+   * is the beginning of the list.
+   * Elements type: pointer to OpenedFile
+   */
+  GSList *stack_files;
+
+  /* The directory where the document is compiled. */
+  gchar *directory_path;
+
+  /* For statistics. */
+  gint nb_badboxes;
+  gint nb_warnings;
+  gint nb_errors;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (LatexilaPostProcessorLatex,
+                            latexila_post_processor_latex,
+                            LATEXILA_TYPE_POST_PROCESSOR)
+
+static OpenedFile *
+opened_file_new (void)
+{
+  return g_slice_new0 (OpenedFile);
+}
+
+static void
+opened_file_free (OpenedFile *opened_file)
+{
+  if (opened_file != NULL)
+    {
+      g_slice_free (OpenedFile, opened_file);
+    }
+}
+
+static gchar *
+get_current_filename (LatexilaPostProcessorLatex *pp)
+{
+  GSList *l;
+
+  for (l = pp->priv->stack_files; l != NULL; l = l->next)
+    {
+      OpenedFile *file = l->data;
+
+      /* TODO check lazily if the file exists */
+      if (file->exists)
+        {
+          return g_strdup (file->filename);
+        }
+    }
+
+  return NULL;
+}
+
+static void
+add_message (LatexilaPostProcessorLatex *pp,
+             gboolean                    set_filename)
+{
+  static GRegex *regex_spaces = NULL;
+  LatexilaBuildMsg *cur_msg;
+  GError *error = NULL;
+
+  cur_msg = pp->priv->cur_msg;
+  g_return_if_fail (cur_msg != NULL);
+
+  /* Exclude some useless messages. */
+  if (cur_msg->type == LATEXILA_BUILD_MSG_TYPE_WARNING &&
+      g_strcmp0 (cur_msg->text, "There were undefined references.") == 0)
+    {
+      latexila_build_msg_reinit (cur_msg);
+      return;
+    }
+
+  if (set_filename)
+    {
+      g_free (cur_msg->filename);
+      cur_msg->filename = get_current_filename (pp);
+    }
+
+  if (G_UNLIKELY (regex_spaces == NULL))
+    {
+      regex_spaces = g_regex_new ("\\s{2,}", G_REGEX_OPTIMIZE, 0, &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+        }
+    }
+
+  if (G_LIKELY (regex_spaces != NULL))
+    {
+      gchar *new_text;
+
+      /* A message on several lines is sometimes indented, so when the lines are
+       * concatenated there are a lot of spaces. So multiple spaces are replaced
+       * by one space.
+       */
+      new_text = g_regex_replace (regex_spaces,
+                                  cur_msg->text,
+                                  -1, 0, " ", 0,
+                                  &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+        }
+
+      if (new_text != NULL)
+        {
+          g_free (cur_msg->text);
+          cur_msg->text = new_text;
+        }
+    }
+
+  switch (cur_msg->type)
+    {
+    case LATEXILA_BUILD_MSG_TYPE_BADBOX:
+      pp->priv->nb_badboxes++;
+      break;
+
+    case LATEXILA_BUILD_MSG_TYPE_WARNING:
+      pp->priv->nb_warnings++;
+      break;
+
+    case LATEXILA_BUILD_MSG_TYPE_ERROR:
+      pp->priv->nb_errors++;
+      break;
+
+    default:
+      break;
+    }
+
+  pp->priv->last_message = g_node_insert_data_after (pp->priv->messages,
+                                                     pp->priv->last_message,
+                                                     cur_msg);
+
+  pp->priv->cur_msg = latexila_build_msg_new ();
+}
+
+static void
+latexila_post_processor_latex_start (LatexilaPostProcessor *post_processor,
+                                     GFile                 *file)
+{
+  LatexilaPostProcessorLatex *pp = LATEXILA_POST_PROCESSOR_LATEX (post_processor);
+  GFile *parent;
+
+  parent = g_file_get_parent (file);
+
+  g_free (pp->priv->directory_path);
+  pp->priv->directory_path = g_file_get_parse_name (parent);
+
+  g_object_unref (parent);
+}
+
+static void
+latexila_post_processor_latex_end (LatexilaPostProcessor *post_processor)
+{
+  LatexilaPostProcessorLatex *pp = LATEXILA_POST_PROCESSOR_LATEX (post_processor);
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+
+  latexila_build_msg_reinit (cur_msg);
+
+  /* Statistics. Since all the messages printed by TeX are in English, the
+   * string is not translated.
+   */
+  cur_msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
+  cur_msg->text = g_strdup_printf ("%d %s, %d %s, %d %s",
+                                   pp->priv->nb_errors,
+                                   pp->priv->nb_errors == 1 ? "error" : "errors",
+                                   pp->priv->nb_warnings,
+                                   pp->priv->nb_warnings == 1 ? "warning" : "warnings",
+                                   pp->priv->nb_badboxes,
+                                   pp->priv->nb_badboxes == 1 ? "badbox" : "badboxes");
+
+  add_message (pp, FALSE);
+}
+
+static gboolean
+detect_badbox_line (LatexilaPostProcessorLatex *pp,
+                    const gchar                *badbox_line,
+                    gboolean                    current_line_is_empty)
+{
+  static GRegex *regex_badbox_lines = NULL;
+  static GRegex *regex_badbox_line = NULL;
+  static GRegex *regex_badbox_output = NULL;
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_badbox_lines == NULL))
+    {
+      regex_badbox_lines = g_regex_new ("(.*) at lines (\\d+)--(\\d+)",
+                                        G_REGEX_OPTIMIZE,
+                                        0,
+                                        &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_badbox_line == NULL))
+    {
+      regex_badbox_line = g_regex_new ("(.*) at line (\\d+)",
+                                       G_REGEX_OPTIMIZE,
+                                       0,
+                                       &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_badbox_output == NULL))
+    {
+      regex_badbox_output = g_regex_new ("(.*)has occurred while \\\\output is active",
+                                         G_REGEX_OPTIMIZE,
+                                         0,
+                                         &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+          return FALSE;
+        }
+    }
+
+  if (g_regex_match (regex_badbox_lines, badbox_line, 0, NULL))
+    {
+      gchar **strings;
+      gint n1;
+      gint n2;
+
+      pp->priv->state = STATE_START;
+
+      /* TODO use g_match_info_fetch_named() */
+      strings = g_regex_split (regex_badbox_lines, badbox_line, 0);
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (strings[1]);
+
+      n1 = atoi (strings[2]);
+      n2 = atoi (strings[3]);
+
+      if (n1 <= n2)
+        {
+          cur_msg->start_line = n1;
+          cur_msg->end_line = n2;
+        }
+      else
+        {
+          cur_msg->start_line = n2;
+          cur_msg->end_line = n1;
+        }
+
+      g_strfreev (strings);
+      return TRUE;
+    }
+
+  if (g_regex_match (regex_badbox_line, badbox_line, 0, NULL))
+    {
+      gchar **strings;
+
+      pp->priv->state = STATE_START;
+
+      strings = g_regex_split (regex_badbox_line, badbox_line, 0);
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (strings[1]);
+
+      cur_msg->start_line = atoi (strings[2]);
+
+      g_strfreev (strings);
+      return TRUE;
+    }
+
+  if (g_regex_match (regex_badbox_output, badbox_line, 0, NULL))
+    {
+      gchar **strings;
+
+      pp->priv->state = STATE_START;
+
+      strings = g_regex_split (regex_badbox_output, badbox_line, 0);
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (strings[1]);
+      cur_msg->start_line = NO_LINE;
+
+      g_strfreev (strings);
+      return TRUE;
+    }
+
+  if (pp->priv->nb_lines > 4 || current_line_is_empty)
+    {
+      pp->priv->state = STATE_START;
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (badbox_line);
+
+      cur_msg->start_line = NO_LINE;
+      return TRUE;
+    }
+
+  pp->priv->state = STATE_BADBOX;
+  return FALSE;
+}
+
+static gboolean
+detect_badbox (LatexilaPostProcessorLatex *pp,
+               const gchar                *line)
+{
+  static GRegex *regex_badbox = NULL;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_badbox == NULL))
+    {
+      regex_badbox = g_regex_new ("^(Over|Under)full \\\\[hv]box",
+                                  G_REGEX_OPTIMIZE,
+                                  0,
+                                  &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          error = NULL;
+          return FALSE;
+        }
+    }
+
+  switch (pp->priv->state)
+    {
+    case STATE_START:
+      if (!g_regex_match (regex_badbox, line, 0, NULL))
+        {
+          return FALSE;
+        }
+
+      pp->priv->cur_msg->type = LATEXILA_BUILD_MSG_TYPE_BADBOX;
+
+      if (detect_badbox_line (pp, line, FALSE))
+        {
+          add_message (pp, TRUE);
+        }
+      else
+        {
+          if (pp->priv->line_buffer != NULL)
+            {
+              g_string_free (pp->priv->line_buffer, TRUE);
+            }
+
+          pp->priv->line_buffer = g_string_new (line);
+          pp->priv->nb_lines++;
+        }
+
+      return TRUE;
+
+    case STATE_BADBOX:
+      g_string_append (pp->priv->line_buffer, line);
+      pp->priv->nb_lines++;
+
+      if (detect_badbox_line (pp,
+                              pp->priv->line_buffer->str,
+                              line[0] == '\0'))
+        {
+          add_message (pp, TRUE);
+          pp->priv->nb_lines = 0;
+        }
+
+      /* The return value is not important here. */
+      return TRUE;
+
+    default:
+      return FALSE;
+    }
+}
+
+static gboolean
+detect_warning_line (LatexilaPostProcessorLatex *pp,
+                     const gchar                *warning,
+                     gboolean                    current_line_is_empty)
+{
+  static GRegex *regex_warning_line = NULL;
+  static GRegex *regex_warning_international_line = NULL;
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+  gint len;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_warning_line == NULL))
+    {
+      regex_warning_line = g_regex_new ("(.*) on input line (\\d+)\\.$",
+                                        G_REGEX_OPTIMIZE,
+                                        0,
+                                        &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_warning_international_line == NULL))
+    {
+      regex_warning_international_line = g_regex_new ("(.*)(\\d+)\\.$",
+                                                      G_REGEX_OPTIMIZE,
+                                                      0,
+                                                      &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (g_regex_match (regex_warning_line, warning, 0, NULL))
+    {
+      gchar **strings;
+
+      pp->priv->state = STATE_START;
+
+      strings = g_regex_split (regex_warning_line, warning, 0);
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (strings[1]);
+
+      cur_msg->start_line = atoi (strings[2]);
+
+      g_strfreev (strings);
+      return TRUE;
+    }
+
+  if (g_regex_match (regex_warning_international_line, warning, 0, NULL))
+    {
+      gchar **strings;
+
+      pp->priv->state = STATE_START;
+
+      strings = g_regex_split (regex_warning_international_line, warning, 0);
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (strings[1]);
+
+      cur_msg->start_line = atoi (strings[2]);
+
+      g_strfreev (strings);
+      return TRUE;
+    }
+
+  len = strlen (warning);
+  if (warning[len-1] == '.' || pp->priv->nb_lines > 5 || current_line_is_empty)
+    {
+      pp->priv->state = STATE_START;
+
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (warning);
+
+      cur_msg->start_line = NO_LINE;
+      return TRUE;
+    }
+
+  pp->priv->state = STATE_WARNING;
+  return FALSE;
+}
+
+static gboolean
+detect_warning (LatexilaPostProcessorLatex *pp,
+                const gchar                *line)
+{
+  static GRegex *regex_warning = NULL;
+  static GRegex *regex_warning_no_file = NULL;
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+  GMatchInfo *match_info;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_warning == NULL))
+    {
+      regex_warning = g_regex_new ("^(((! )?(La|pdf)TeX)|Package|Class)"
+                                   "(?P<name>.*) Warning[^:]*:\\s*(?P<contents>.*)",
+                                   G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
+                                   0,
+                                   &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_warning_no_file == NULL))
+    {
+      regex_warning_no_file = g_regex_new ("(No file .*)",
+                                           G_REGEX_OPTIMIZE,
+                                           0,
+                                           &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  switch (pp->priv->state)
+    {
+    case STATE_START:
+      g_regex_match (regex_warning, line, 0, &match_info);
+      if (g_match_info_matches (match_info))
+        {
+          gchar *contents;
+          gchar *name;
+
+          cur_msg->type = LATEXILA_BUILD_MSG_TYPE_WARNING;
+
+          contents = g_match_info_fetch_named (match_info, "contents");
+          name = g_match_info_fetch_named (match_info, "name");
+          name = g_strstrip (name);
+
+          if (name[0] != '\0')
+            {
+              gchar *new_contents = g_strdup_printf ("%s: %s", name, contents);
+              g_free (contents);
+              contents = new_contents;
+            }
+
+          if (detect_warning_line (pp, contents, FALSE))
+            {
+              add_message (pp, TRUE);
+            }
+          else
+            {
+              if (pp->priv->line_buffer != NULL)
+                {
+                  g_string_free (pp->priv->line_buffer, TRUE);
+                }
+
+              pp->priv->line_buffer = g_string_new (contents);
+              pp->priv->nb_lines++;
+            }
+
+          g_free (contents);
+          g_free (name);
+          g_match_info_free (match_info);
+          return TRUE;
+        }
+
+      g_match_info_free (match_info);
+      match_info = NULL;
+
+      if (g_regex_match (regex_warning_no_file, line, 0, NULL))
+        {
+          gchar **strings;
+
+          cur_msg->type = LATEXILA_BUILD_MSG_TYPE_WARNING;
+
+          strings = g_regex_split (regex_warning_no_file, line, 0);
+
+          g_free (cur_msg->text);
+          cur_msg->text = g_strdup (strings[1]);
+
+          cur_msg->start_line = NO_LINE;
+
+          add_message (pp, TRUE);
+
+          g_strfreev (strings);
+          return TRUE;
+        }
+
+      return FALSE;
+
+    case STATE_WARNING:
+      g_string_append (pp->priv->line_buffer, line);
+      pp->priv->nb_lines++;
+
+      if (detect_warning_line (pp,
+                               pp->priv->line_buffer->str,
+                               line[0] == '\0'))
+        {
+          add_message (pp, TRUE);
+          pp->priv->nb_lines = 0;
+        }
+
+      /* The return value is not important here. */
+      return TRUE;
+
+    default:
+      return FALSE;
+    }
+}
+
+static gboolean
+detect_error (LatexilaPostProcessorLatex *pp,
+              const gchar                *line)
+{
+  static GRegex *regex_latex_error = NULL;
+  static GRegex *regex_pdflatex_error = NULL;
+  static GRegex *regex_tex_error = NULL;
+  static GRegex *regex_error_line = NULL;
+  gboolean found;
+  gchar *msg;
+  gint len;
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_latex_error == NULL))
+    {
+      regex_latex_error = g_regex_new ("^! LaTeX Error: (.*)$",
+                                       G_REGEX_OPTIMIZE,
+                                       0,
+                                       &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_pdflatex_error == NULL))
+    {
+      regex_pdflatex_error = g_regex_new ("^Error: pdflatex (.*)$",
+                                          G_REGEX_OPTIMIZE,
+                                          0,
+                                          &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_tex_error == NULL))
+    {
+      regex_tex_error = g_regex_new ("^! (.*)\\.$",
+                                     G_REGEX_OPTIMIZE,
+                                     0,
+                                     &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (G_UNLIKELY (regex_error_line == NULL))
+    {
+      regex_error_line = g_regex_new ("^l\\.(\\d+)(.*)",
+                                      G_REGEX_OPTIMIZE,
+                                      0,
+                                      &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  switch (pp->priv->state)
+    {
+    case STATE_START:
+      found = TRUE;
+      msg = NULL;
+
+      if (g_regex_match (regex_latex_error, line, 0, NULL))
+        {
+          gchar **strings = g_regex_split (regex_latex_error, line, 0);
+          msg = g_strdup (strings[1]);
+          g_strfreev (strings);
+        }
+      else if (g_regex_match (regex_pdflatex_error, line, 0, NULL))
+        {
+          gchar **strings = g_regex_split (regex_pdflatex_error, line, 0);
+          msg = g_strdup (strings[1]);
+          g_strfreev (strings);
+        }
+      else if (g_regex_match (regex_tex_error, line, 0, NULL))
+        {
+          gchar **strings = g_regex_split (regex_tex_error, line, 0);
+          msg = g_strdup (strings[1]);
+          g_strfreev (strings);
+        }
+      else
+        {
+          found = FALSE;
+        }
+
+      if (found)
+        {
+          pp->priv->nb_lines++;
+          cur_msg->type = LATEXILA_BUILD_MSG_TYPE_ERROR;
+
+          len = strlen (line);
+
+          /* The message is complete. */
+          if (line[len-1] == '.')
+            {
+              g_free (cur_msg->text);
+              cur_msg->text = msg;
+
+              pp->priv->state = STATE_ERROR_SEARCH_LINE;
+            }
+
+          /* The message is split into several lines. */
+          else
+            {
+              if (pp->priv->line_buffer != NULL)
+                {
+                  g_string_free (pp->priv->line_buffer, TRUE);
+                }
+
+              pp->priv->line_buffer = g_string_new (msg);
+              pp->priv->state = STATE_ERROR;
+
+              g_free (msg);
+            }
+
+          return TRUE;
+        }
+
+      return FALSE;
+
+    case STATE_ERROR:
+      g_string_append (pp->priv->line_buffer, line);
+      pp->priv->nb_lines++;
+
+      len = strlen (line);
+
+      if (line[len-1] == '.')
+        {
+          g_free (cur_msg->text);
+          cur_msg->text = g_string_free (pp->priv->line_buffer, FALSE);
+          pp->priv->line_buffer = NULL;
+
+          pp->priv->state = STATE_ERROR_SEARCH_LINE;
+        }
+      else if (pp->priv->nb_lines > 4)
+        {
+          g_free (cur_msg->text);
+          cur_msg->text = g_string_free (pp->priv->line_buffer, FALSE);
+          pp->priv->line_buffer = NULL;
+
+          cur_msg->start_line = NO_LINE;
+
+          add_message (pp, TRUE);
+
+          pp->priv->nb_lines = 0;
+          pp->priv->state = STATE_START;
+        }
+
+      /* The return value is not important here. */
+      return TRUE;
+
+    case STATE_ERROR_SEARCH_LINE:
+      pp->priv->nb_lines++;
+
+      if (g_regex_match (regex_error_line, line, 0, NULL))
+        {
+          gchar **strings = g_regex_split (regex_error_line, line, 0);
+          cur_msg->start_line = atoi (strings[1]);
+
+          add_message (pp, TRUE);
+
+          pp->priv->nb_lines = 0;
+          pp->priv->state = STATE_START;
+
+          g_strfreev (strings);
+          return TRUE;
+        }
+      else if (pp->priv->nb_lines > 11)
+        {
+          cur_msg->start_line = NO_LINE;
+          add_message (pp, TRUE);
+          pp->priv->nb_lines = 0;
+          pp->priv->state = STATE_START;
+          return TRUE;
+        }
+      break;
+
+    default:
+      break;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+detect_other (LatexilaPostProcessorLatex *pp,
+              const gchar                *line)
+{
+  static GRegex *regex_other_bytes = NULL;
+  LatexilaBuildMsg *cur_msg = pp->priv->cur_msg;
+  GMatchInfo *match_info;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_other_bytes == NULL))
+    {
+      regex_other_bytes = g_regex_new ("(?P<nb>\\d+) bytes",
+                                       G_REGEX_OPTIMIZE,
+                                       0,
+                                       &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return FALSE;
+        }
+    }
+
+  if (strstr (line, "Output written on") == NULL)
+    return FALSE;
+
+  cur_msg->start_line = NO_LINE;
+  cur_msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
+
+  g_regex_match (regex_other_bytes, line, 0, &match_info);
+
+  if (g_match_info_matches (match_info))
+    {
+      gchar *nb_bytes_str;
+      glong nb_bytes;
+      gchar *human_size;
+      gchar *new_line;
+
+      nb_bytes_str = g_match_info_fetch_named (match_info, "nb");
+      g_return_val_if_fail (nb_bytes_str != NULL, FALSE);
+
+      nb_bytes = atol (nb_bytes_str);
+      human_size = g_format_size (nb_bytes);
+
+      new_line = g_regex_replace_literal (regex_other_bytes, line, -1, 0, human_size, 0, &error);
+
+      if (error == NULL)
+        {
+          g_free (cur_msg->text);
+          cur_msg->text = new_line;
+        }
+      else
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+
+          g_free (cur_msg->text);
+          cur_msg->text = g_strdup (line);
+        }
+
+      g_free (nb_bytes_str);
+      g_free (human_size);
+    }
+  else
+    {
+      g_free (cur_msg->text);
+      cur_msg->text = g_strdup (line);
+    }
+
+  add_message (pp, FALSE);
+
+  g_match_info_free (match_info);
+  return TRUE;
+}
+
+static void
+push_file_on_stack (LatexilaPostProcessorLatex *pp,
+                    const gchar                *filename,
+                    gboolean                    reliable)
+{
+}
+
+static void
+pop_file_from_stack (LatexilaPostProcessorLatex *pp)
+{
+  if (pp->priv->stack_files != NULL)
+    {
+      OpenedFile *opened_file = pp->priv->stack_files->data;
+      opened_file_free (opened_file);
+    }
+
+  pp->priv->stack_files = g_slist_remove_link (pp->priv->stack_files,
+                                               pp->priv->stack_files);
+}
+
+static void
+update_stack_file_heuristic (LatexilaPostProcessorLatex *pp,
+                             const gchar                *line)
+{
+}
+
+/* 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 an 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 (LatexilaPostProcessorLatex *pp,
+                   const gchar                *line)
+{
+  static GRegex *regex_file_pop = NULL;
+  GError *error = NULL;
+
+  if (G_UNLIKELY (regex_file_pop == NULL))
+    {
+      regex_file_pop = g_regex_new ("(\\) )?:<-$",
+                                    G_REGEX_OPTIMIZE,
+                                    0,
+                                    &error);
+
+      if (error != NULL)
+        {
+          g_warning ("PostProcessorLatex: %s", error->message);
+          g_error_free (error);
+          return;
+        }
+    }
+
+  switch (pp->priv->state)
+    {
+    case STATE_START:
+    case STATE_FILENAME_HEURISTIC:
+      /* TeX is opening a file. */
+      if (g_str_has_prefix (line, ":<+ "))
+        {
+          gchar *filename;
+
+          if (pp->priv->filename_buffer != NULL)
+            g_string_free (pp->priv->filename_buffer, TRUE);
+
+          filename = g_strdup (line + 4);
+          g_strstrip (filename);
+
+          pp->priv->filename_buffer = g_string_new (filename);
+          pp->priv->state = STATE_FILENAME;
+
+          g_free (filename);
+        }
+
+      /* TeX closed a file. */
+      else if (g_regex_match (regex_file_pop, line, 0, NULL) ||
+               g_str_has_prefix (line, ":<-"))
+        pop_file_from_stack (pp);
+
+      /* Fallback to the heuristic detection of filenames. */
+      else
+        update_stack_file_heuristic (pp, line);
+      break;
+
+    case STATE_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 (pp, pp->priv->filename_buffer->str, TRUE);
+          pp->priv->state = STATE_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] == '!')
+        {
+          pp->priv->state = STATE_START;
+          detect_error (pp, line);
+        }
+      else if (g_str_has_prefix (line, "No file"))
+        {
+          pp->priv->state = STATE_START;
+          detect_warning (pp, line);
+        }
+
+      /* The filename is not complete. */
+      else
+        {
+          gchar *line_stripped = g_strdup (line);
+          g_strstrip (line_stripped);
+          g_string_append (pp->priv->filename_buffer, line_stripped);
+          g_free (line_stripped);
+        }
+
+    default:
+      break;
+    }
+}
+
+static void
+process_line (LatexilaPostProcessorLatex *pp,
+              const gchar                *line)
+{
+  g_assert (line != NULL);
+
+  switch (pp->priv->state)
+    {
+    case STATE_START:
+      if (line[0] == '\0')
+        {
+          return;
+        }
+
+      if (!(detect_badbox (pp, line) ||
+            detect_warning (pp, line) ||
+            detect_error (pp, line) ||
+            detect_other (pp, line)))
+        {
+          update_stack_file (pp, line);
+        }
+      break;
+
+    case STATE_BADBOX:
+      detect_badbox (pp, line);
+      break;
+
+    case STATE_WARNING:
+      detect_warning (pp, line);
+      break;
+
+    case STATE_ERROR:
+    case STATE_ERROR_SEARCH_LINE:
+      detect_error (pp, line);
+      break;
+
+    case STATE_FILENAME:
+    case STATE_FILENAME_HEURISTIC:
+      update_stack_file (pp, line);
+      break;
+
+    default:
+      pp->priv->state = STATE_START;
+      break;
+    }
+}
+
+static void
+latexila_post_processor_latex_process_lines (LatexilaPostProcessor  *post_processor,
+                                             gchar                 **lines)
+{
+  LatexilaPostProcessorLatex *pp = LATEXILA_POST_PROCESSOR_LATEX (post_processor);
+  gint i;
+
+  for (i = 0; lines != NULL && lines[i] != NULL; i++)
+    {
+      process_line (pp, lines[i]);
+      g_free (lines[i]);
+    }
+
+  g_free (lines);
+}
+
+static const GNode *
+latexila_post_processor_latex_get_messages (LatexilaPostProcessor *post_processor)
+{
+  LatexilaPostProcessorLatex *pp = LATEXILA_POST_PROCESSOR_LATEX (post_processor);
+
+  return pp->priv->messages->children;
+}
+
+static void
+latexila_post_processor_latex_finalize (GObject *object)
+{
+  LatexilaPostProcessorLatex *pp = LATEXILA_POST_PROCESSOR_LATEX (object);
+
+  latexila_build_messages_free (pp->priv->messages);
+
+  if (pp->priv->cur_msg != NULL)
+    {
+      latexila_build_msg_free (pp->priv->cur_msg);
+    }
+
+  if (pp->priv->line_buffer != NULL)
+    {
+      g_string_free (pp->priv->line_buffer, TRUE);
+    }
+
+  if (pp->priv->filename_buffer != NULL)
+    {
+      g_string_free (pp->priv->filename_buffer, TRUE);
+    }
+
+  g_slist_free_full (pp->priv->stack_files, (GDestroyNotify) opened_file_free);
+
+  g_free (pp->priv->directory_path);
+
+  G_OBJECT_CLASS (latexila_post_processor_latex_parent_class)->finalize (object);
+}
+
+static void
+latexila_post_processor_latex_class_init (LatexilaPostProcessorLatexClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  LatexilaPostProcessorClass *pp_class = LATEXILA_POST_PROCESSOR_CLASS (klass);
+
+  object_class->finalize = latexila_post_processor_latex_finalize;
+
+  pp_class->start = latexila_post_processor_latex_start;
+  pp_class->end = latexila_post_processor_latex_end;
+  pp_class->process_lines = latexila_post_processor_latex_process_lines;
+  pp_class->get_messages = latexila_post_processor_latex_get_messages;
+}
+
+static void
+latexila_post_processor_latex_init (LatexilaPostProcessorLatex *pp)
+{
+  pp->priv = latexila_post_processor_latex_get_instance_private (pp);
+
+  pp->priv->cur_msg = latexila_build_msg_new ();
+  pp->priv->state = STATE_START;
+}
diff --git a/src/liblatexila/latexila-post-processor-latex.h b/src/liblatexila/latexila-post-processor-latex.h
new file mode 100644
index 0000000..bcd617a
--- /dev/null
+++ b/src/liblatexila/latexila-post-processor-latex.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright (C) 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * LaTeXila is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LaTeXila is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LATEXILA_POST_PROCESSOR_LATEX_H__
+#define __LATEXILA_POST_PROCESSOR_LATEX_H__
+
+#include <glib-object.h>
+#include "latexila-post-processor.h"
+#include "latexila-types.h"
+
+G_BEGIN_DECLS
+
+#define LATEXILA_TYPE_POST_PROCESSOR_LATEX             (latexila_post_processor_latex_get_type ())
+#define LATEXILA_POST_PROCESSOR_LATEX(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
LATEXILA_TYPE_POST_PROCESSOR_LATEX, LatexilaPostProcessorLatex))
+#define LATEXILA_POST_PROCESSOR_LATEX_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), 
LATEXILA_TYPE_POST_PROCESSOR_LATEX, LatexilaPostProcessorLatexClass))
+#define LATEXILA_IS_POST_PROCESSOR_LATEX(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
LATEXILA_TYPE_POST_PROCESSOR_LATEX))
+#define LATEXILA_IS_POST_PROCESSOR_LATEX_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), 
LATEXILA_TYPE_POST_PROCESSOR_LATEX))
+#define LATEXILA_POST_PROCESSOR_LATEX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), 
LATEXILA_TYPE_POST_PROCESSOR_LATEX, LatexilaPostProcessorLatexClass))
+
+typedef struct _LatexilaPostProcessorLatexClass   LatexilaPostProcessorLatexClass;
+typedef struct _LatexilaPostProcessorLatexPrivate LatexilaPostProcessorLatexPrivate;
+
+struct _LatexilaPostProcessorLatex
+{
+  LatexilaPostProcessor parent;
+
+  LatexilaPostProcessorLatexPrivate *priv;
+};
+
+struct _LatexilaPostProcessorLatexClass
+{
+  LatexilaPostProcessorClass parent_class;
+};
+
+GType latexila_post_processor_latex_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __LATEXILA_POST_PROCESSOR_LATEX_H__ */
diff --git a/src/liblatexila/latexila-post-processor.c b/src/liblatexila/latexila-post-processor.c
index 07f395c..74f69c4 100644
--- a/src/liblatexila/latexila-post-processor.c
+++ b/src/liblatexila/latexila-post-processor.c
@@ -238,12 +238,25 @@ latexila_post_processor_finalize (GObject *object)
 }
 
 static void
+latexila_post_processor_start_default (LatexilaPostProcessor *pp,
+                                       GFile                 *file)
+{
+  /* Do nothing. */
+}
+
+static void
 latexila_post_processor_process_lines_default (LatexilaPostProcessor  *pp,
                                                gchar                 **lines)
 {
   g_strfreev (lines);
 }
 
+static void
+latexila_post_processor_end_default (LatexilaPostProcessor *pp)
+{
+  /* Do nothing. */
+}
+
 static const GNode *
 latexila_post_processor_get_messages_default (LatexilaPostProcessor *pp)
 {
@@ -260,7 +273,9 @@ latexila_post_processor_class_init (LatexilaPostProcessorClass *klass)
   object_class->dispose = latexila_post_processor_dispose;
   object_class->finalize = latexila_post_processor_finalize;
 
+  klass->start = latexila_post_processor_start_default;
   klass->process_lines = latexila_post_processor_process_lines_default;
+  klass->end = latexila_post_processor_end_default;
   klass->get_messages = latexila_post_processor_get_messages_default;
 
   g_object_class_install_property (object_class,
@@ -412,6 +427,7 @@ read_stream (LatexilaPostProcessor *pp)
 /**
  * latexila_post_processor_process_async:
  * @pp: a post-processor.
+ * @file: the #GFile on which the build tool is run.
  * @stream: the input stream to process.
  * @cancellable: a #GCancellable.
  * @callback: the callback to call when the operation is finished.
@@ -426,12 +442,14 @@ read_stream (LatexilaPostProcessor *pp)
  */
 void
 latexila_post_processor_process_async (LatexilaPostProcessor *pp,
+                                       GFile                 *file,
                                        GInputStream          *stream,
                                        GCancellable          *cancellable,
                                        GAsyncReadyCallback    callback,
                                        gpointer               user_data)
 {
   g_return_if_fail (LATEXILA_IS_POST_PROCESSOR (pp));
+  g_return_if_fail (G_IS_FILE (file));
   g_return_if_fail (G_IS_INPUT_STREAM (stream));
   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
   g_return_if_fail (pp->priv->task == NULL);
@@ -439,6 +457,8 @@ latexila_post_processor_process_async (LatexilaPostProcessor *pp,
   pp->priv->task = g_task_new (pp, cancellable, callback, user_data);
   pp->priv->stream = g_object_ref (stream);
 
+  LATEXILA_POST_PROCESSOR_GET_CLASS (pp)->start (pp, file);
+
   if (pp->priv->line_buffer != NULL)
     {
       g_string_free (pp->priv->line_buffer, TRUE);
@@ -465,6 +485,8 @@ latexila_post_processor_process_finish (LatexilaPostProcessor *pp,
 
   g_task_propagate_boolean (G_TASK (result), NULL);
 
+  LATEXILA_POST_PROCESSOR_GET_CLASS (pp)->end (pp);
+
   g_clear_object (&pp->priv->task);
   g_clear_object (&pp->priv->stream);
 
diff --git a/src/liblatexila/latexila-post-processor.h b/src/liblatexila/latexila-post-processor.h
index 3f244f9..43e02d4 100644
--- a/src/liblatexila/latexila-post-processor.h
+++ b/src/liblatexila/latexila-post-processor.h
@@ -65,9 +65,24 @@ struct _LatexilaPostProcessorClass
 {
   GObjectClass parent_class;
 
+  /* Start of processing.
+   * @file is the GFile on which the build tool is run.
+   */
+  void (* start) (LatexilaPostProcessor *pp,
+                  GFile                 *file);
+
+  /* The process_lines function takes ownership of @lines. Free with
+   * g_strfreev() if you don't reuse the contents.
+   */
   void (* process_lines) (LatexilaPostProcessor  *pp,
                           gchar                 **lines);
 
+  /* End of processing. */
+  void (* end) (LatexilaPostProcessor *pp);
+
+  /* Get the build messages. The elements are of type "LatexilaBuildMsg *".
+   * This function is called after end().
+   */
   const GNode * (* get_messages) (LatexilaPostProcessor *pp);
 };
 
@@ -81,6 +96,7 @@ const gchar *           latexila_post_processor_get_name_from_type    (LatexilaP
 void                    latexila_build_messages_free                  (GNode *build_messages);
 
 void                    latexila_post_processor_process_async         (LatexilaPostProcessor *pp,
+                                                                       GFile                 *file,
                                                                        GInputStream          *stream,
                                                                        GCancellable          *cancellable,
                                                                        GAsyncReadyCallback    callback,
diff --git a/src/liblatexila/latexila-types.h b/src/liblatexila/latexila-types.h
index 1dd9da4..997aa99 100644
--- a/src/liblatexila/latexila-types.h
+++ b/src/liblatexila/latexila-types.h
@@ -32,6 +32,7 @@ typedef struct _LatexilaBuildToolsPersonal      LatexilaBuildToolsPersonal;
 typedef struct _LatexilaBuildView               LatexilaBuildView;
 typedef struct _LatexilaPostProcessor           LatexilaPostProcessor;
 typedef struct _LatexilaPostProcessorAllOutput  LatexilaPostProcessorAllOutput;
+typedef struct _LatexilaPostProcessorLatex      LatexilaPostProcessorLatex;
 typedef struct _LatexilaSynctex                 LatexilaSynctex;
 
 G_END_DECLS
diff --git a/src/liblatexila/latexila.h b/src/liblatexila/latexila.h
index a200ab9..34607ad 100644
--- a/src/liblatexila/latexila.h
+++ b/src/liblatexila/latexila.h
@@ -31,6 +31,7 @@
 #include "latexila-build-view.h"
 #include "latexila-post-processor.h"
 #include "latexila-post-processor-all-output.h"
+#include "latexila-post-processor-latex.h"
 #include "latexila-synctex.h"
 #include "latexila-utils.h"
 



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