=?utf-8?q?=5Bfolks=5D_Bug_660236_=E2=80=94_Paging_for_long_text_in_folks-?= =?utf-8?q?inspect?=
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Bug 660236 â Paging for long text in folks-inspect
- Date: Mon, 23 Jul 2012 23:35:51 +0000 (UTC)
commit 19f77e2a6ca09fb74635c5387bd2e9a98aa7bfe3
Author: Philip Withnall <philip tecnocode co uk>
Date: Sat Jul 21 01:25:01 2012 +0100
Bug 660236 â Paging for long text in folks-inspect
Use $PAGER to allow paging of the output of really long commands in
folks-inspect.
Based on work by RaÃl Gutierrez Segales and Jeremy Whiting.
Closes: https://bugzilla.gnome.org/show_bug.cgi?id=660236
NEWS | 1 +
tools/inspect/Makefile.am | 1 +
tools/inspect/inspect.vala | 320 ++++++++++++++++++++++++++++++++++++--------
tools/inspect/utils.vala | 6 +-
4 files changed, 268 insertions(+), 60 deletions(-)
---
diff --git a/NEWS b/NEWS
index 10b41ad..d586eed 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,7 @@ Bugs fixed:
â Bug 675223 â Shouldn't warn if CM does not implement any contact list
â Bug 660128 â Most contacts don't have an avatar
â Bug 652637 â Don't hold locks across async calls
+â Bug 660236 â Paging for long text in folks-inspect
API changes:
â Add AntiLinkable interface and implement it on Kf.Persona and Edsf.Persona
diff --git a/tools/inspect/Makefile.am b/tools/inspect/Makefile.am
index f341e4a..65a1b79 100644
--- a/tools/inspect/Makefile.am
+++ b/tools/inspect/Makefile.am
@@ -6,6 +6,7 @@ VALAFLAGS = \
--pkg=gobject-2.0 \
--pkg=gio-2.0 \
--pkg=gee-0.8 \
+ --pkg=posix \
--pkg=folks \
--pkg=build-conf \
$(NULL)
diff --git a/tools/inspect/inspect.vala b/tools/inspect/inspect.vala
index 9d66234..c3fe2a2 100644
--- a/tools/inspect/inspect.vala
+++ b/tools/inspect/inspect.vala
@@ -24,6 +24,7 @@ using Folks;
using Readline;
using Gee;
using GLib;
+using Posix;
/* We have to have a static global instance so that the readline callbacks can
* access its data, since they don't pass closures around. */
@@ -32,11 +33,22 @@ static Inspect.Client main_client = null;
public class Folks.Inspect.Client : Object
{
public HashMap<string, Command> commands;
+ private static bool _is_readline_installed;
private MainLoop main_loop;
public IndividualAggregator aggregator { get; private set; }
public BackendStore backend_store { get; private set; }
public SignalManager signal_manager { get; private set; }
+ /* To page or not to page? */
+ private termios _original_termios_p;
+ private bool _original_termios_p_valid = false;
+ private bool _quit_after_pager_dies = false;
+ private static Pid _pager_pid = 0;
+ private IOChannel? _stdin_channel = null;
+ private static uint _stdin_watch_id = 0;
+ private FileStream? _pager_channel = null;
+ private uint _pager_child_watch_id = 0;
+
public static int main (string[] args)
{
Intl.bindtextdomain (BuildConf.GETTEXT_PACKAGE, BuildConf.LOCALE_DIR);
@@ -52,7 +64,7 @@ public class Folks.Inspect.Client : Object
}
catch (OptionError e1)
{
- stderr.printf ("Couldnât parse command line options: %s\n",
+ GLib.stderr.printf ("Couldnât parse command line options: %s\n",
e1.message);
return 1;
}
@@ -63,8 +75,17 @@ public class Folks.Inspect.Client : Object
/* Set up signal handling. */
Unix.signal_add (Posix.SIGTERM, () =>
{
- /* Quit the client and let that exit the process. */
- main_client.quit ();
+ /* Propagate the signal to our pager process, if it's running. */
+ if (main_client._pager_pid != 0)
+ {
+ main_client._quit_after_pager_dies = true;
+ kill (main_client._pager_pid, Posix.SIGTERM);
+ }
+ else
+ {
+ /* Quit the client and let that exit the process. */
+ main_client.quit ();
+ }
return false;
});
@@ -76,7 +97,7 @@ public class Folks.Inspect.Client : Object
}
else
{
- assert (args.length > 1);
+ GLib.assert (args.length > 1);
/* Drop the first argument and parse the rest as a command line. If
* the first argument is â--â then the command was passed after some
@@ -124,6 +145,23 @@ public class Folks.Inspect.Client : Object
public void quit ()
{
+ /* Stop paging. */
+ this._stop_paged_output ();
+
+ /* Uninstall readline, if it's installed. */
+ if (this._is_readline_installed)
+ {
+ this._uninstall_readline_and_stdin ();
+ }
+
+ /* Restore the user's original terminal settings, since the pager might've
+ * fiddled with them. */
+ if (this._original_termios_p_valid)
+ {
+ tcsetattr (Posix.STDIN_FILENO, Posix.TCSADRAIN,
+ this._original_termios_p);
+ }
+
/* Kill the main loop. */
this.main_loop.quit ();
}
@@ -153,7 +191,7 @@ public class Folks.Inspect.Client : Object
finally
{
this.aggregator.disconnect (signal_id);
- assert (this.aggregator.is_quiescent == true);
+ GLib.assert (this.aggregator.is_quiescent == true);
}
}
@@ -171,7 +209,7 @@ public class Folks.Inspect.Client : Object
if (command == null)
{
- stdout.printf ("Unrecognised command â%sâ.\n", command_name);
+ GLib.stdout.printf ("Unrecognised command â%sâ.\n", command_name);
return;
}
@@ -185,14 +223,14 @@ public class Folks.Inspect.Client : Object
}
catch (GLib.Error e1)
{
- stderr.printf ("Error preparing aggregator: %s\n", e1.message);
+ GLib.stderr.printf ("Error preparing aggregator: %s\n", e1.message);
Process.exit (1);
}
/* Run the command */
command.run (subcommand);
- this.main_loop.quit ();
+ this.quit ();
});
this.main_loop.run ();
@@ -205,30 +243,28 @@ public class Folks.Inspect.Client : Object
* stdin in our main loop, and passing character notifications to
* readline. The main loop also processes all the folks events, thus
* preventing us having to run a second thread. */
- var stdin_channel = new IOChannel.unix_new (stdin.fileno ());
- stdin_channel.add_watch (IOCondition.IN, (source, cond) =>
- {
- /* At least a single character is available on stdin, so let readline
- * consume it. */
- if ((cond & IOCondition.IN) != 0)
- {
- Readline.callback_read_char ();
- return true;
- }
- assert_not_reached ();
- });
+ /* Copy the user's original terminal settings. */
+ if (tcgetattr (Posix.STDIN_FILENO, out this._original_termios_p) == 0)
+ {
+ this._original_termios_p_valid = true;
+ }
/* Handle SIGINT. */
Unix.signal_add (Posix.SIGINT, () =>
{
+ if (Client._is_readline_installed == false)
+ {
+ return true;
+ }
+
/* Tidy up. */
Readline.free_line_state ();
Readline.cleanup_after_signal ();
Readline.reset_after_signal ();
/* Display a fresh prompt. */
- stdout.printf ("^C");
+ GLib.stdout.printf ("^C");
Readline.crlf ();
Readline.reset_line_state ();
Readline.replace_line ("", 0);
@@ -243,60 +279,226 @@ public class Folks.Inspect.Client : Object
Readline.attempted_completion_function = Client.completion_cb;
Readline.catch_signals = 0; /* go away, readline */
+ /* Install readline and the stdin handler. */
+ this._stdin_channel = new IOChannel.unix_new (GLib.stdin.fileno ());
+ this._install_readline_and_stdin ();
+
+ /* Run the aggregator and the main loop. */
+ this.aggregator.prepare ();
+
+ this.main_loop.run ();
+ }
+
+ private void _install_readline_and_stdin ()
+ {
+ /* stdin handler. */
+ this._stdin_watch_id = this._stdin_channel.add_watch (IOCondition.IN,
+ this._stdin_handler_cb);
+
/* Callback for each character appearing on stdin. */
- Readline.callback_handler_install ("> ", (_command_line) =>
- {
- if (_command_line == null)
- {
- /* EOF. If we've entered some text, don't do anything. Otherwise,
- * quit. */
- if (Readline.line_buffer != "")
- {
- Readline.ding ();
- return;
- }
+ Readline.callback_handler_install ("> ", Client._readline_handler_cb);
+ Client._is_readline_installed = true;
+ }
- /* Quit. */
- Readline.crlf ();
- Readline.callback_handler_remove ();
- main_client.quit ();
+ private void _uninstall_readline_and_stdin ()
+ {
+ Readline.callback_handler_remove ();
+ Client._is_readline_installed = false;
- return;
- }
+ Source.remove (this._stdin_watch_id);
+ this._stdin_watch_id = 0;
+ }
+
+ /* This should only ever be called while readline is installed. */
+ private bool _stdin_handler_cb (IOChannel source, IOCondition cond)
+ {
+ /* At least a single character is available on stdin, so let readline
+ * consume it. */
+ if ((cond & IOCondition.IN) != 0)
+ {
+ Readline.callback_read_char ();
+ return true;
+ }
- var command_line = (!) _command_line;
+ assert_not_reached ();
+ }
- command_line = command_line.strip ();
- if (command_line == "")
+ private static void _readline_handler_cb (string? _command_line)
+ {
+ if (_command_line == null)
+ {
+ /* EOF. If we've entered some text, don't do anything. Otherwise,
+ * quit. */
+ if (Readline.line_buffer != "")
{
- /* If the user's entered a blank line, just display a new prompt
- * without doing anything else. */
+ Readline.ding ();
return;
}
- string subcommand;
- string command_name;
- Command command = main_client.parse_command_line (command_line,
- out command_name, out subcommand);
+ /* Quit. */
+ main_client.quit ();
- /* Run the command */
- if (command != null)
+ return;
+ }
+
+ var command_line = (!) _command_line;
+
+ command_line = command_line.strip ();
+ if (command_line == "")
+ {
+ /* If the user's entered a blank line, just display a new prompt
+ * without doing anything else. */
+ return;
+ }
+
+ string subcommand;
+ string command_name;
+ Command command = main_client.parse_command_line (command_line,
+ out command_name, out subcommand);
+
+ /* Run the command */
+ if (command != null)
+ {
+ if (command_name != "quit")
{
- command.run (subcommand);
+ /* Start paging output. This is stopped when the pager dies. */
+ main_client._start_paged_output ();
}
- else
+
+ command.run (subcommand);
+
+ /* Close the stream to the pager so it knows it's reached EOF. */
+ main_client._pager_channel = null;
+ Utils.output_filestream = GLib.stdout;
+ }
+ else
+ {
+ GLib.stdout.printf ("Unrecognised command â%sâ.\n", command_name);
+ }
+
+ /* Store the command in the history, even if it failed */
+ Readline.History.add (command_line);
+ }
+
+ private void _start_paged_output ()
+ {
+ /* If the output is not a TTY (because it's a pipe or a file or a
+ * toaster) we don't page. */
+ if (!isatty (1))
+ {
+ return;
+ }
+
+ var pager = Environment.get_variable ("PAGER");
+ if (pager != null && pager == "")
+ {
+ return;
+ }
+
+ if (pager == null)
+ {
+ pager = "less -FRSX";
+ }
+
+ /* Convert command to null terminated array */
+ string[] args;
+ try
+ {
+ GLib.Shell.parse_argv (pager, out args);
+ }
+ catch (GLib.ShellError e)
+ {
+ warning ("Error parsing pager arguments: %s", e.message);
+ return;
+ }
+
+ /* Remove the readline and stdin handlers while the pager is running. */
+ this._uninstall_readline_and_stdin ();
+
+ /* Store the readline terminal state so that we can restore them
+ * after the pager has exited. */
+ Readline.prep_terminal (1);
+
+ /* Spawn the pager. */
+ int pager_fd = 0;
+
+ try
+ {
+ GLib.Process.spawn_async_with_pipes (null,
+ args,
+ null,
+ GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
+ GLib.SpawnFlags.SEARCH_PATH |
+ GLib.SpawnFlags.DO_NOT_REAP_CHILD /* we use a ChildWatch */,
+ null,
+ out this._pager_pid,
+ out pager_fd, // Std input
+ null, // Std out
+ null); // Std error
+ }
+ catch (SpawnError e2)
+ {
+ warning ("Error spawning pager: %s", e2.message);
+ return;
+ }
+
+ /* Redirect our output to the pager. */
+ this._pager_channel = FileStream.fdopen (pager_fd, "w");
+ Utils.output_filestream = this._pager_channel;
+
+ /* Watch for when the pager exits. */
+ this._pager_child_watch_id = ChildWatch.add (this._pager_pid,
+ (pid, status) =>
{
- stdout.printf ("Unrecognised command â%sâ.\n", command_name);
- }
+ /* $PAGER died or was killed. */
+ this._stop_paged_output ();
+
+ /* Reset the readline state ready to display a new prompt. If the
+ * pager exited as the result of a signal, it probably didn't
+ * tidy up after itself (e.g. `less` leaves a colon prompt behind
+ * on the current line), so move to a new line. Doing this
+ * normally just looks a bit weird. */
+ if (Process.if_signaled (status))
+ {
+ Readline.crlf ();
+ }
- /* Store the command in the history, even if it failed */
- Readline.History.add (command_line);
- });
+ Readline.reset_line_state ();
+ Readline.replace_line ("", 0);
- /* Run the aggregator and the main loop. */
- this.aggregator.prepare ();
+ /* Are we supposed to quit (e.g. due to receiving a SIGTERM)? */
+ if (this._quit_after_pager_dies)
+ {
+ main_client.quit ();
+ return;
+ }
- this.main_loop.run ();
+ /* Reinstall the readline handler and stdin handler. */
+ this._install_readline_and_stdin ();
+ });
+ }
+
+ private void _stop_paged_output ()
+ {
+ if (this._pager_pid == 0)
+ {
+ return;
+ }
+
+ Process.close_pid (this._pager_pid);
+ Source.remove (this._pager_child_watch_id);
+
+ this._pager_channel = null;
+ Utils.output_filestream = GLib.stdout;
+ this._pager_pid = 0;
+ this._pager_child_watch_id = 0;
+
+ /* Reset the terminal state (e.g. ECHO, which can get left turned
+ * off if the pager was killed uncleanly). */
+ Readline.deprep_terminal ();
+ Readline.free_line_state ();
+ Readline.cleanup_after_signal ();
+ Readline.reset_after_signal ();
}
private static Command? parse_command_line (string command_line,
diff --git a/tools/inspect/utils.vala b/tools/inspect/utils.vala
index 3774e63..1aeccc2 100644
--- a/tools/inspect/utils.vala
+++ b/tools/inspect/utils.vala
@@ -28,6 +28,9 @@ private class Folks.Inspect.Utils
private static uint indentation = 0;
private static string indentation_string = "";
+ /* The FILE we're printing output to. */
+ public static unowned FileStream output_filestream = GLib.stdout;
+
public static void init ()
{
Utils.indentation_string = "";
@@ -107,7 +110,8 @@ private class Folks.Inspect.Utils
/* FIXME: store the va_list temporarily to work around bgo#638308 */
var valist = va_list ();
string output = format.vprintf (valist);
- stdout.printf ("%s%s\n", Utils.indentation_string, output);
+ var str = "%s%s\n".printf (Utils.indentation_string, output);
+ Utils.output_filestream.printf (str);
}
public static void print_individual (Individual individual,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]