lib/sup/modes/edit-message-mode.rb in sup-0.12.1 vs lib/sup/modes/edit-message-mode.rb in sup-0.13.0

- old
+ new

@@ -78,23 +78,23 @@ k.add :edit_message_or_field, "Edit selected field", 'e' k.add :edit_to, "Edit To:", 't' k.add :edit_cc, "Edit Cc:", 'c' k.add :edit_subject, "Edit Subject", 's' k.add :edit_message, "Edit message", :enter + k.add :edit_message_async, "Edit message asynchronously", 'E' k.add :save_as_draft, "Save as draft", 'P' k.add :attach_file, "Attach a file", 'a' k.add :delete_attachment, "Delete an attachment", 'd' k.add :move_cursor_right, "Move selector to the right", :right, 'l' k.add :move_cursor_left, "Move selector to the left", :left, 'h' end def initialize opts={} - @header = opts.delete(:header) || {} + @header = opts.delete(:header) || {} @header_lines = [] @body = opts.delete(:body) || [] - @body += sig_lines if $config[:edit_signature] && !opts.delete(:have_signature) if opts[:attachments] @attachments = opts[:attachments].values @attachment_names = opts[:attachments].keys else @@ -109,30 +109,56 @@ end hostname = Socket.gethostname if hostname.nil? or hostname.empty? @message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{hostname}>" @edited = false + @sig_edited = false @selectors = [] @selector_label_width = 0 + @async_mode = nil + HookManager.run "before-edit", :header => @header, :body => @body + + @account_selector = nil + # only show account selector if there is more than one email address + if $config[:account_selector] && AccountManager.user_emails.length > 1 + ## Duplicate e-mail strings to prevent a "can't modify frozen + ## object" crash triggered by the String::display_length() + ## method in util.rb + user_emails_copy = [] + AccountManager.user_emails.each { |e| user_emails_copy.push e.dup } + + @account_selector = + HorizontalSelector.new "Account:", AccountManager.user_emails + [nil], user_emails_copy + ["Customized"] + + if @header["From"] =~ /<?(\S+@(\S+?))>?$/ + @account_selector.set_to $1 + @account_user = "" + else + @account_selector.set_to nil + @account_user = @header["From"] + end + + add_selector @account_selector + end + @crypto_selector = if CryptoManager.have_crypto? HorizontalSelector.new "Crypto:", [:none] + CryptoManager::OUTGOING_MESSAGE_OPERATIONS.keys, ["None"] + CryptoManager::OUTGOING_MESSAGE_OPERATIONS.values end add_selector @crypto_selector if @crypto_selector - - HookManager.run "before-edit", :header => @header, :body => @body + if @crypto_selector HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector end super opts regen_text end def lines; @text.length + (@selectors.empty? ? 0 : (@selectors.length + DECORATION_LINES)) end - + def [] i if @selectors.empty? @text[i] elsif i < @selectors.length @selectors[i].line @selector_label_width @@ -159,34 +185,117 @@ def edit_to; edit_field "To" end def edit_cc; edit_field "Cc" end def edit_subject; edit_field "Subject" end - def edit_message - @file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}" + def save_message_to_file + sig = sig_lines.join("\n") + @file = Tempfile.new ["sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}", ".eml"] @file.puts format_headers(@header - NON_EDITABLE_HEADERS).first @file.puts @file.puts @body.join("\n") + @file.puts sig if ($config[:edit_signature] and !@sig_edited) @file.close + end + def set_sig_edit_flag + sig = sig_lines.join("\n") + if $config[:edit_signature] + pbody = @body.join("\n") + blen = pbody.length + slen = sig.length + + if blen > slen and pbody[blen-slen..blen] == sig + @sig_edited = false + @body = pbody[0..blen-slen].split("\n") + else + @sig_edited = true + end + end + end + + def edit_message + old_from = @header["From"] if @account_selector + + begin + save_message_to_file + rescue SystemCallError => e + BufferManager.flash "Can't save message to file: #{e.message}" + return + end + editor = $config[:editor] || ENV['EDITOR'] || "/usr/bin/vi" mtime = File.mtime @file.path BufferManager.shell_out "#{editor} #{@file.path}" @edited = true if File.mtime(@file.path) > mtime return @edited unless @edited header, @body = parse_file @file.path @header = header - NON_EDITABLE_HEADERS + set_sig_edit_flag + + if @account_selector and @header["From"] != old_from + @account_user = @header["From"] + @account_selector.set_to nil + end + handle_new_text @header, @body + rerun_crypto_selector_hook update @edited end + def edit_message_async + begin + save_message_to_file + rescue SystemCallError => e + BufferManager.flash "Can't save message to file: #{e.message}" + return + end + + @mtime = File.mtime @file.path + + # put up buffer saying you can now edit the message in another + # terminal or app, and continue to use sup in the meantime. + subject = @header["Subject"] || "" + @async_mode = EditMessageAsyncMode.new self, @file.path, subject + BufferManager.spawn "Waiting for message \"#{subject}\" to be finished", @async_mode + + # hide ourselves, and wait for signal to resume from async mode ... + buffer.hidden = true + end + + def edit_message_async_resume being_killed=false + buffer.hidden = false + @async_mode = nil + BufferManager.raise_to_front buffer if !being_killed + + @edited = true if File.mtime(@file.path) > @mtime + + header, @body = parse_file @file.path + @header = header - NON_EDITABLE_HEADERS + set_sig_edit_flag + handle_new_text @header, @body + update + + true + end + def killable? + if !@async_mode.nil? + return false if !@async_mode.killable? + if File.mtime(@file.path) > @mtime + @edited = true + header, @body = parse_file @file.path + @header = header - NON_EDITABLE_HEADERS + handle_new_text @header, @body + update + end + end !edited? || BufferManager.ask_yes_or_no("Discard message?") end def unsaved?; edited? end @@ -203,20 +312,26 @@ BufferManager.flash "Can't read #{fn}: #{e.message}" end end def delete_attachment - i = curpos - @attachment_lines_offset - DECORATION_LINES - 1 + i = curpos - @attachment_lines_offset - DECORATION_LINES - 2 if i >= 0 && i < @attachments.size && BufferManager.ask_yes_or_no("Delete attachment #{@attachment_names[i]}?") @attachments.delete_at i @attachment_names.delete_at i update end end protected + def rerun_crypto_selector_hook + if @crypto_selector && !@crypto_selector.changed_by_user + HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector + end + end + def mime_encode string string = [string].pack('M') # basic quoted-printable string.gsub!(/=\n/,'') # .. remove trailing newline string.gsub!(/_/,'=5F') # .. encode underscores string.gsub!(/\?/,'=3F') # .. encode question marks @@ -240,19 +355,21 @@ def move_cursor_left if curpos < @selectors.length @selectors[curpos].roll_left buffer.mark_dirty + update if @account_selector else col_left end end def move_cursor_right if curpos < @selectors.length @selectors[curpos].roll_right buffer.mark_dirty + update if @account_selector else col_right end end @@ -260,19 +377,27 @@ @selectors << s @selector_label_width = [@selector_label_width, s.label.length].max end def update + if @account_selector + if @account_selector.val.nil? + @header["From"] = @account_user + else + @header["From"] = AccountManager.full_address_for @account_selector.val + end + end + regen_text buffer.mark_dirty if buffer end def regen_text header, @header_lines = format_headers(@header - NON_EDITABLE_HEADERS) + [""] @text = header + [""] + @body - @text += sig_lines unless $config[:edit_signature] - + @text += sig_lines unless @sig_edited + @attachment_lines_offset = 0 unless @attachments.empty? @text += [""] @attachment_lines_offset = @text.length @@ -337,11 +462,11 @@ def send_message return false if !edited? && !BufferManager.ask_yes_or_no("Message unedited. Really send?") return false if $config[:confirm_no_attachments] && mentions_attachments? && @attachments.size == 0 && !BufferManager.ask_yes_or_no("You haven't added any attachments. Really send?")#" stupid ruby-mode return false if $config[:confirm_top_posting] && top_posting? && !BufferManager.ask_yes_or_no("You're top-posting. That makes you a bad person. Really send?") #" stupid ruby-mode - from_email = + from_email = if @header["From"] =~ /<?(\S+@(\S+?))>?$/ $1 else AccountManager.default_account.email end @@ -382,20 +507,20 @@ def build_message date m = RMail::Message.new m.header["Content-Type"] = "text/plain; charset=#{$encoding}" m.body = @body.join("\n") - m.body += sig_lines.join("\n") unless $config[:edit_signature] + m.body += "\n" + sig_lines.join("\n") unless @sig_edited ## body must end in a newline or GPG signatures will be WRONG! m.body += "\n" unless m.body =~ /\n\Z/ ## there are attachments, so wrap body in an attachment of its own unless @attachments.empty? body_m = m body_m.header["Content-Disposition"] = "inline" m = RMail::Message.new - + m.add_part body_m @attachments.each { |a| m.add_part a } end ## do whatever crypto transformation is necessary @@ -412,11 +537,11 @@ end ## finally, set the top-level headers @header.each do |k, v| next if v.nil? || v.empty? - m.header[k] = + m.header[k] = case v when String k.match(/subject/i) ? mime_encode_subject(v) : mime_encode_address(v) when Array v.map { |v| mime_encode_address v }.join ", " @@ -452,11 +577,11 @@ end f.puts f.puts sanitize_body(@body.join("\n")) f.puts sig_lines if full unless $config[:edit_signature] - end + end protected def edit_field field case field @@ -477,10 +602,17 @@ contacts = BufferManager.ask_for_contacts :people, "#{field}: ", default if contacts text = contacts.map { |s| s.full_address }.join(", ") @header[field] = parse_header field, text + + if @account_selector and field == "From" + @account_user = @header["From"] + @account_selector.set_to nil + end + + rerun_crypto_selector_hook update end end end @@ -512,10 +644,10 @@ return [] if hook_sig == :none return ["", "-- "] + hook_sig.split("\n") if hook_sig ## no hook, do default signature generation based on config.yaml return [] unless from_email - sigfn = (AccountManager.account_for(from_email) || + sigfn = (AccountManager.account_for(from_email) || AccountManager.default_account).signature if sigfn && File.exists?(sigfn) ["", "-- "] + File.readlines(sigfn).map { |l| l.chomp } else