Subject: Re: [sup-talk] [PATCH] mutt-style pipe to interactive process support

From: Marc Hartstein <[email-masked]>
Date: Sat, 26 Jun 2010 12:37:06 -0400

Code review would be highly appreciated on this one. I did some research
and checking and I'm fairly confident that I got the multiprogramming
aspects correct, but I haven't done much with forked processes before,
so I'd appreciate another set of eyes on it.

As always, I'm receptive to style review to best integrate with the
predominant Sup programming style as well.

The primary motivation for this patch is that the one thing I've missed
most in moving from mutt is being able to use urlview to quickly open
urls in my browser. While urxvt helps me with short urls, anything
longer than my current viewport line gets broken in a way urxvt can't
detect, so I decided it was time to make the pipe command more powerful.

I've left the old code pathways fully intact.
_______________________________________________
sup-talk mailing list
[email-masked]
http://rubyforge.org/mailman/listinfo/sup-talk

---

From: Marc Hartstein <[email-masked]>
Date: Sat, 26 Jun 2010 12:30:07 -0400

Adds a new method maybe_interactive_pipe_message to ThreadViewMode which will
pipe a message to a process in the same manner as the mutt mutt_pipe_message
command, allowing interactive tools such as 'urlview' to reopen the tty for
IO.

Terminating your pipe command with the pipe character ('|') for "pipe back to
sup" will use the original behavior, capturing the output of the pipeline for
display in a sup buffer. Calling maybe_interactive_pipe_message with
maybe_interactive=false, or calling pipe_message will always use the old
behavior.

No keymap is provided in this patch. It is recommended to replace pipe_message
on '|' in keybindings.rb using a line like:

Redwood::ThreadViewMode::keymap.add! :maybe_interactive_pipe_message, "Pipe
message or attachment to an interactive shell command", '|'
---
 lib/sup/mode.rb                   |   32 ++++++++++++++++++++++++
 lib/sup/modes/thread-view-mode.rb |   48 +++++++++++++++++++++++++++++-------
 2 files changed, 70 insertions(+), 10 deletions(-)

diff --git a/lib/sup/mode.rb b/lib/sup/mode.rb
index f5aee1c..8d6197d 100644
--- a/lib/sup/mode.rb
+++ b/lib/sup/mode.rb
@@ -101,6 +101,38 @@ EOS
     end
   end
 
+  def pipe_to_interactive_process command
+    read, write = IO.pipe
+
+    child_pid = fork
+    if child_pid
+      # main process
+      begin
+        read.close
+        yield write
+      rescue
+        warn "error writing to #{command}: #{$!}"
+        BufferManager.flash "error writing to #{command}: #{$!}"
+      ensure
+        write.close
+        Process.waitpid(child_pid)
+      end
+    else
+      # child
+      begin
+        write.close
+        $stdin.reopen(read)
+        exec(command)
+      rescue
+        # Can't access logger from child process, but can flash an error
+        BufferManager.flash "error running #{command}: #{$!}"
+      ensure
+        read.close
+        Kernel.exit!(127)
+      end
+    end
+  end
+
   def pipe_to_process command
     Open3.popen3(command) do |input, output, error|
       err, data, * = IO.select [error], [input], nil
diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread-view-mode.rb
index 088529b..bd7908c 100644
--- a/lib/sup/modes/thread-view-mode.rb
+++ b/lib/sup/modes/thread-view-mode.rb
@@ -661,6 +661,10 @@ EOS
   private :dispatch
 
   def pipe_message
+    maybe_interactive_pipe_message false
+  end
+
+  def maybe_interactive_pipe_message maybe_interactive=true
     chunk = @chunk_lines[curpos]
     chunk = nil unless chunk.is_a?(Chunk::Attachment)
     message = @message_lines[curpos] unless chunk
@@ -669,20 +673,44 @@ EOS
 
     command = BufferManager.ask(:shell, "pipe command: ")
     return if command.nil? || command.empty?
+    if maybe_interactive and command[-1,1]=="|"
+      command = command.chop.strip
+      return if command.empty?
+      interactive = false
+    else
+      interactive = maybe_interactive
+    end
+
+    if interactive
+      pipe_to_interactive_process(command) do |stream|
+        if chunk
+          stream.print chunk.raw_content
+        else
+          message.each_raw_message_line { |l| 
+            begin
+              stream.print l
+            rescue
+              warn "error writing to #{command}: #{$!}"
+              BufferManager.flash "error writing to #{command}: #{$!}"
+              break
+            end }
+        end
+      end
+    else
+      output = pipe_to_process(command) do |stream|
+        if chunk
+          stream.print chunk.raw_content
+        else
+          message.each_raw_message_line { |l| stream.print l }
+        end
+      end
 
-    output = pipe_to_process(command) do |stream|
-      if chunk
-        stream.print chunk.raw_content
+      if output
+        BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
       else
-        message.each_raw_message_line { |l| stream.print l }
+        BufferManager.flash "'#{command}' done!"
       end
     end
-
-    if output
-      BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
-    else
-      BufferManager.flash "'#{command}' done!"
-    end
   end
 
 private
-- 
1.6.4.4