Logo Search packages:      
Sourcecode: latexila version File versions  Download package

external_commands.c

/*
 * This file is part of LaTeXila.
 *
 * Copyright © 2009, 2010 Sébastien Wilmet
 *
 * 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/>.
 */

#define _POSIX_C_SOURCE 1
#include <string.h>
#include <sys/wait.h>
#include <unistd.h> // for dup2
#include <sys/types.h>
#include <signal.h> // for kill
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gstdio.h>

#include "main.h"
#include "callbacks.h"
#include "config.h"
#include "print.h"
#include "utils.h"
#include "external_commands.h"
#include "file_browser.h"
#include "latex_output_filter.h"
#include "log.h"

static void compile_document (gchar *title, gchar **command);
static void set_action_sensitivity (gboolean sensitive);
static gchar * get_command_line (gchar **command);
static void start_command_without_output (gchar **command, gchar *message);
static void start_command_with_output (gchar **command);
static void cb_spawn_setup (gpointer data);
static gboolean cb_watch_output_command (GIOChannel *channel,
            GIOCondition condition, gpointer user_data);
static void cb_child_watch (GPid pid, gint status, gpointer user_data);
static void finish_execute (void);
static void run_command_on_other_extension (gchar *title, gchar *message,
            gchar *command, gchar *extension);
static gboolean is_current_doc_tex_file (void);

static gboolean show_all_output = TRUE;
static GPid child_pid;
static gint child_pid_exit_code = 0;
static gboolean exit_code_set = FALSE;
static enum output output_status = OUTPUT_GO_FETCHING;

void
run_compilation (gchar *command, gchar *title)
{
      cb_save ();

      // if it was a new document, and if the user has cancelled the saving
      if (latexila.active_doc->path == NULL)
            return;

      if (latexila.prefs.compile_non_stop)
      {
            gchar *argv[] = {
                  command,
                  "-interaction=nonstopmode",

                  // we take the basename because the command is run inside the directory
                  // of the document, and the output lines which contains the filename
                  // are shorter (the lines too long are splitted, so the user can not
                  // see all the line if there is a filter which operate line per line)
                  latexila.active_doc->basename,
                  NULL
            };
            compile_document (title, argv);
      }

      else
      {
            gchar *argv[] = {
                  command,
                  latexila.active_doc->basename,
                  NULL
            };
            compile_document (title, argv);
      }
}

static void
compile_document (gchar *title, gchar **command)
{
      if (latexila.active_doc == NULL)
            return;

      gchar *command_line = get_command_line (command);
      add_action (title, command_line);
      g_free (command_line);

      if (! is_current_doc_tex_file ())
            return;

      /* print a message in the statusbar */
      guint context_id = gtk_statusbar_get_context_id (latexila.statusbar,
                  "running-action");
      gtk_statusbar_push (latexila.statusbar, context_id,
                  _("Compilation in progress. Please wait..."));

      // without that, the message in the statusbar does not appear
      flush_queue ();

      show_all_output = latexila.prefs.compile_show_all_output;
      start_command_with_output (command);
}

void
view_current_document (gchar *title, gchar *doc_extension)
{
      if (latexila.active_doc == NULL)
            return;

      /* replace .tex by doc_extension (.pdf, .dvi, ...) */
      GRegex *regex = g_regex_new ("\\.tex$", 0, 0, NULL);
      gchar *doc_path = g_regex_replace_literal (regex, latexila.active_doc->path,
                  -1, 0, doc_extension, 0, NULL);
      g_regex_unref (regex);

      gchar *command_line = g_strdup_printf ("%s %s", latexila.prefs.command_view,
                  doc_path);
      add_action (title, command_line);
      g_free (command_line);

      if (! is_current_doc_tex_file ())
      {
            g_free (doc_path);
            return;
      }

      /* the document (PDF, DVI, ...) file exist? */
      if (! g_file_test (doc_path, G_FILE_TEST_IS_REGULAR))
      {
            gchar *command_output = g_strdup_printf (
                        _("%s does not exist. If this is not already made, compile the document with the right command."),
                        g_path_get_basename (doc_path));
            print_output_exit (1337, command_output);
            g_free (command_output);
            g_free (doc_path);
            return;
      }

      gchar *command[] = {latexila.prefs.command_view, doc_path, NULL};
      start_command_without_output (command, NULL);
      g_free (doc_path);
}

void
view_document (gchar *title, gchar *filename)
{
      gchar *command_line = g_strdup_printf ("%s %s", latexila.prefs.command_view,
                  filename);
      add_action (title, command_line);
      g_free (command_line);

      gchar *command[] = {latexila.prefs.command_view, filename, NULL};
      start_command_without_output (command, NULL);
}

void
convert_document (gchar *title, gchar *doc_extension, gchar *command)
{
      run_command_on_other_extension (
                  title,
                  _("Converting in progress. Please wait..."),
                  command,
                  doc_extension);
}

void
run_bibtex (void)
{
      run_command_on_other_extension (
                  "BibTeX",
                  _("BibTeX is running. Please wait..."),
                  latexila.prefs.command_bibtex,
                  ".aux");
}

void
run_makeindex (void)
{
      run_command_on_other_extension (
                  "MakeIndex",
                  _("MakeIndex is running. Please wait..."),
                  latexila.prefs.command_makeindex,
                  ".idx");
}

void
view_in_web_browser (gchar *title, gchar *filename)
{
      gchar *command_line = g_strdup_printf ("%s %s",
                  latexila.prefs.command_web_browser, filename);
      add_action (title, command_line);
      g_free (command_line);

      gchar *command[] = {latexila.prefs.command_web_browser, filename, NULL};
      start_command_without_output (command, NULL);
}

void
stop_execution (void)
{
      kill (child_pid, SIGTERM);
      gtk_action_set_sensitive (latexila.actions.stop_execution, FALSE);
}

static void
set_action_sensitivity (gboolean sensitive)
{
      // Lock the history action list when a command is running so the user can not view
      // an other action. We do that because the output lines are added in the
      // current list store. If the user changed the action in the history list
      // while a new action is running, the new output lines would be added to the
      // wrong list store.
      set_history_sensitivity (sensitive);

      gtk_action_set_sensitive (latexila.actions.compile_latex, sensitive);
      gtk_action_set_sensitive (latexila.actions.compile_pdflatex, sensitive);
      gtk_action_set_sensitive (latexila.actions.dvi_to_pdf, sensitive);
      gtk_action_set_sensitive (latexila.actions.dvi_to_ps, sensitive);
      gtk_action_set_sensitive (latexila.actions.view_dvi, sensitive);
      gtk_action_set_sensitive (latexila.actions.view_pdf, sensitive);
      gtk_action_set_sensitive (latexila.actions.view_ps, sensitive);
      gtk_action_set_sensitive (latexila.actions.bibtex, sensitive);
      gtk_action_set_sensitive (latexila.actions.makeindex, sensitive);
      gtk_action_set_sensitive (latexila.actions.stop_execution, ! sensitive);
}

static gchar *
get_command_line (gchar **command)
{
      if (command[0] == NULL)
            return NULL;

      gchar *command_line = g_strdup (command[0]);
      gchar *tmp;
      gchar **arg = command;
      arg++;
      while (*arg != NULL)
      {
            tmp = g_strdup_printf ("%s %s", command_line, *arg);
            g_free (command_line);
            command_line = tmp;
            arg++;
      }

      return command_line;
}

static void
start_command_without_output (gchar **command, gchar *message)
{
      GError *error = NULL;
      g_spawn_async (NULL, command, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);

      if (error != NULL)
      {
            gchar *command_output = g_strdup_printf (_("execution failed: %s"),
                        error->message);
            print_output_exit (42, command_output);
            g_free (command_output);
            g_error_free (error);
      }
      else if (message == NULL)
            print_output_info (_("Viewing in progress. Please wait..."));
      else
            print_output_info (message);
}

// Attention, before calling this function, set the variable "show_all_output"
// to TRUE or FALSE. If it is FALSE, the output will be filtered by the function
// latex_output_filter().
static void
start_command_with_output (gchar **command)
{
      gchar *dir = g_path_get_dirname (latexila.active_doc->path);
      GError *error = NULL;
      gint out;
      g_spawn_async_with_pipes (dir, command, NULL,
                  G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
                  (GSpawnChildSetupFunc) cb_spawn_setup, NULL,
                  &child_pid, NULL, &out, NULL, &error);

      // an error occured
      if (error != NULL)
      {
            gchar *command_output = g_strdup_printf (_("execution failed: %s"),
                        error->message);
            print_output_exit (42, command_output);
            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;

      // we want to know the exit code
      g_child_watch_add (child_pid, (GChildWatchFunc) cb_child_watch, NULL);

      // create the channel
      GIOChannel *out_channel = g_io_channel_unix_new (out);

      // convert the channel
      g_io_channel_set_encoding (out_channel, NULL, NULL);

      // lock the action list and all the build actions
      set_action_sensitivity (FALSE);

      // add watches to channels
      g_io_add_watch (out_channel, G_IO_IN | G_IO_HUP,
                  (GIOFunc) cb_watch_output_command, NULL);
}

static void
cb_spawn_setup (gpointer data)
{
      // include stderr in the output
      dup2 (STDOUT_FILENO, STDERR_FILENO);
}

static gboolean
cb_watch_output_command (GIOChannel *channel, GIOCondition condition,
            gpointer user_data)
{
      switch (output_status)
      {
            case OUTPUT_GO_FETCHING:
                  break;

            case OUTPUT_IS_FETCHING:
                  return FALSE;
                  break;

            case OUTPUT_STOP_REQUEST:
                  g_io_channel_unref (channel);
                  finish_execute ();
                  return FALSE;
                  break;
      }

      output_status = OUTPUT_IS_FETCHING;

      if (condition & G_IO_IN)
      {
            GError *error = NULL;
            gchar *line = NULL;
            GIOStatus gio_status = g_io_channel_read_line (channel, &line, NULL, NULL, &error); 
            while (gio_status == G_IO_STATUS_NORMAL && output_status == OUTPUT_IS_FETCHING)
            {
                  gchar *line_utf8 = NULL;
                  if (line != NULL)
                  {
                        line_utf8 = g_locale_to_utf8 (line, -1, NULL, NULL, NULL);
                        if (line_utf8 == NULL)
                              line_utf8 = g_convert (line, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
                        g_free (line);
                  }

                  // the line is not showed if it contains bad characters!
                  if (line_utf8 != NULL)
                  {
                        // delete the \n
                        line_utf8[strlen (line_utf8) - 1] = '\0';

                        if (show_all_output)
                              print_output_normal (line_utf8);
                        else
                              latex_output_filter (line_utf8);

                        g_free (line_utf8);
                  }

                  gio_status = g_io_channel_read_line (channel, &line, NULL, NULL, &error);
            }

            if (error != NULL)
            {
                  print_warning ("IO channel error: %s", error->message);
                  g_error_free (error);
            }
            
            if (gio_status == G_IO_STATUS_EOF)
            {
                  output_status = OUTPUT_STOP_REQUEST;
                  g_io_channel_unref (channel);
                  finish_execute ();
                  return FALSE;
            }
      }
      
      if (condition & G_IO_HUP)
      {
            output_status = OUTPUT_STOP_REQUEST;
            g_io_channel_unref (channel);
            finish_execute ();
            return FALSE;
      }

      if (output_status == OUTPUT_IS_FETCHING)
            output_status = OUTPUT_GO_FETCHING;

      return TRUE;
}

static void
cb_child_watch (GPid pid, gint status, gpointer user_data)
{
      g_spawn_close_pid (pid);

      if (WIFEXITED (status))
            child_pid_exit_code = WEXITSTATUS (status);
      else
            child_pid_exit_code = -1;

      exit_code_set = TRUE;
      finish_execute ();
}

static void
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)
            print_output_exit (child_pid_exit_code, NULL);
      else
            print_output_exit (42, _("The child process exited abnormally"));

      output_view_columns_autosize ();

      // unlock the action list and all the build actions
      set_action_sensitivity (TRUE);

      // pop the message from the statusbar
      guint context_id = gtk_statusbar_get_context_id (latexila.statusbar,
                  "running-action");
      gtk_statusbar_pop (latexila.statusbar, context_id);

      cb_file_browser_refresh (NULL, NULL);
}

/* Run a command on the current document but with an other extension.
 * For example the current document is doc.tex, and we want to run
 * "bibtex doc.aux". In this case, command will be "bibtex" and extension
 * ".aux".
 * The output of the command is displayed.
 */
static void
run_command_on_other_extension (gchar *title, gchar *message, gchar *command,
            gchar *extension)
{
      if (latexila.active_doc == NULL)
            return;

      gchar *command_output;

      /* replace .tex by .aux */
      GRegex *regex = g_regex_new ("\\.tex$", 0, 0, NULL);
      gchar *doc_path = g_regex_replace_literal (regex,
                  latexila.active_doc->path, -1, 0, extension, 0, NULL);
      g_regex_unref (regex);

      gchar *full_command = g_strdup_printf ("%s %s", command, doc_path);
      add_action (title, full_command);
      g_free (full_command);

      /* the document with the other extension exist? */
      if (! g_file_test (doc_path, G_FILE_TEST_IS_REGULAR))
      {
            command_output = g_strdup_printf (
                        _("%s does not exist. If this is not already made, compile the document with the right command."),
                        g_path_get_basename (doc_path));
            print_output_exit (42, command_output);
            g_free (command_output);
            g_free (doc_path);
            return;
      }

      /* print a message in the statusbar */
      guint context_id = gtk_statusbar_get_context_id (latexila.statusbar,
                  "running-action");
      gtk_statusbar_push (latexila.statusbar, context_id, message);

      // without that, the message in the statusbar does not appear
      flush_queue ();

      /* run the command */
      gchar *argv[] = {command, doc_path, NULL};
      show_all_output = TRUE;
      start_command_with_output (argv);
      g_free (doc_path);
}

static gboolean
is_current_doc_tex_file (void)
{
      /* the current document is a *.tex file? */
      if (! g_str_has_suffix (latexila.active_doc->path, ".tex"))
      {
            gchar *command_output = g_strdup_printf (_("failed: %s is not a *.tex file"),
                        latexila.active_doc->basename);
            print_output_exit (0, command_output);
            g_free (command_output);
            return FALSE;
      }

      return TRUE;
}

Generated by  Doxygen 1.6.0   Back to index