Class: Redwood::EditMessageMode

Inherits:
LineCursorMode show all
Defined in:
lib/sup/modes/edit_message_mode.rb

Direct Known Subclasses

ComposeMode, ForwardMode, ReplyMode, ResumeMode

Constant Summary collapse

DECORATION_LINES =
1
FORCE_HEADERS =
%w(From To Cc Bcc Subject)
MULTI_HEADERS =
%w(To Cc Bcc)
NON_EDITABLE_HEADERS =
%w(Message-id Date)

Instance Attribute Summary collapse

Attributes inherited from LineCursorMode

#curpos

Attributes inherited from ScrollMode

#botline, #leftcol, #topline

Attributes inherited from Mode

#buffer

Instance Method Summary collapse

Methods inherited from LineCursorMode

#cleanup, #draw

Methods inherited from ScrollMode

#at_bottom?, #at_top?, #cancel_search!, #col_jump, #col_left, #col_right, #continue_search_in_buffer, #draw, #ensure_mode_validity, #half_page_down, #half_page_up, #in_search?, #jump_to_col, #jump_to_end, #jump_to_left, #jump_to_line, #jump_to_start, #line_down, #line_up, #page_down, #page_up, #resize, #rightcol, #search_goto_line, #search_goto_pos, #search_in_buffer, #search_start_line

Methods inherited from Mode

#blur, #cancel_search!, #cleanup, #draw, #focus, #handle_input, #help_text, #in_search?, keymap, keymaps, load_all_modes, make_name, #name, #pipe_to_process, register_keymap, #resize, #resolve_input, #save_to_file

Constructor Details

#initialize(opts = {}) ⇒ EditMessageMode

Returns a new instance of EditMessageMode.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/sup/modes/edit_message_mode.rb', line 101

def initialize opts={}
  @header = opts.delete(:header) || {}
  @header_lines = []

  @body = opts.delete(:body) || []

  if opts[:attachments]
    @attachments = opts[:attachments].values
    @attachment_names = opts[:attachments].keys
  else
    @attachments = []
    @attachment_names = []
  end

  begin
    hostname = File.open("/etc/mailname", "r").gets.chomp
  rescue
    nil
  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+?))>?$/
      # TODO: this is ugly. might implement an AccountSelector and handle
      # special cases more transparently.
       = @account_selector.can_set_to?($1) ? $1 : nil
      @account_selector.set_to 
    else
      @account_selector.set_to nil
    end

    # A single source of truth might better than duplicating this in both
    # @account_user and @account_selector.
    @account_user = @header["From"]

    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

  if @crypto_selector
    HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector
  end

  super opts
  regen_text
end

Instance Attribute Details

#bodyObject

Returns the value of attribute body.



83
84
85
# File 'lib/sup/modes/edit_message_mode.rb', line 83

def body
  @body
end

#headerObject

Returns the value of attribute header.



83
84
85
# File 'lib/sup/modes/edit_message_mode.rb', line 83

def header
  @header
end

#statusObject (readonly)

Returns the value of attribute status.



82
83
84
# File 'lib/sup/modes/edit_message_mode.rb', line 82

def status
  @status
end

Instance Method Details

#[](i) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/sup/modes/edit_message_mode.rb', line 175

def [] i
  if @selectors.empty?
    @text[i]
  elsif i < @selectors.length
    @selectors[i].line @selector_label_width
  elsif i == @selectors.length
    ""
  else
    @text[i - @selectors.length - DECORATION_LINES]
  end
end

#alternate_edit_messageObject



247
248
249
250
251
252
253
# File 'lib/sup/modes/edit_message_mode.rb', line 247

def alternate_edit_message
  if $config[:always_edit_async]
    return edit_message
  else
    return edit_message_async
  end
end

#attach_fileObject



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/sup/modes/edit_message_mode.rb', line 341

def attach_file
  fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): "
  return unless fn
  if HookManager.enabled? "check-attachment"
      reason = HookManager.run("check-attachment", :filename => fn)
      if reason
          return unless BufferManager.ask_yes_or_no("#{reason} Attach anyway?")
      end
  end
  begin
    Dir[fn].each do |f|
      @attachments << RMail::Message.make_file_attachment(f)
      @attachment_names << f
    end
    update
  rescue SystemCallError => e
    BufferManager.flash "Can't read #{fn}: #{e.message}"
  end
end

#default_edit_messageObject



239
240
241
242
243
244
245
# File 'lib/sup/modes/edit_message_mode.rb', line 239

def default_edit_message
  if $config[:always_edit_async]
    return edit_message_async
  else
    return edit_message
  end
end

#delete_attachmentObject



361
362
363
364
365
366
367
368
# File 'lib/sup/modes/edit_message_mode.rb', line 361

def delete_attachment
  i = curpos - @attachment_lines_offset - (@selectors.empty? ? 0 : DECORATION_LINES) - @selectors.size
  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

#edit_ccObject



202
# File 'lib/sup/modes/edit_message_mode.rb', line 202

def edit_cc; edit_field "Cc" end

#edit_messageObject



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/sup/modes/edit_message_mode.rb', line 255

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

#edit_message_asyncObject



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/sup/modes/edit_message_mode.rb', line 289

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

#edit_message_async_resume(being_killed = false) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/sup/modes/edit_message_mode.rb', line 309

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

#edit_message_or_fieldObject



190
191
192
193
194
195
196
197
198
199
# File 'lib/sup/modes/edit_message_mode.rb', line 190

def edit_message_or_field
  lines = (@selectors.empty? ? 0 : DECORATION_LINES) + @selectors.size
  if lines > curpos
    return
  elsif (curpos - lines) >= @header_lines.length
    default_edit_message
  else
    edit_field @header_lines[curpos - lines]
  end
end

#edit_subjectObject



203
# File 'lib/sup/modes/edit_message_mode.rb', line 203

def edit_subject; edit_field "Subject" end

#edit_toObject



201
# File 'lib/sup/modes/edit_message_mode.rb', line 201

def edit_to; edit_field "To" end

#handle_new_text(header, body) ⇒ Object

hook for subclasses. i hate this style of programming.



188
# File 'lib/sup/modes/edit_message_mode.rb', line 188

def handle_new_text header, body; end

#killable?Boolean

Returns:

  • (Boolean)


325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/sup/modes/edit_message_mode.rb', line 325

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

#linesObject



173
# File 'lib/sup/modes/edit_message_mode.rb', line 173

def lines; @text.length + (@selectors.empty? ? 0 : (@selectors.length + DECORATION_LINES)) end

#save_message_to_fileObject



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/sup/modes/edit_message_mode.rb', line 205

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

  begin
    text = @body.join("\n")
  rescue Encoding::CompatibilityError
    text = @body.map { |x| x.fix_encoding! }.join("\n")
    debug "encoding problem while writing message, trying to rescue, but expect errors: #{text}"
  end

  @file.puts text
  @file.puts sig if ($config[:edit_signature] and !@sig_edited)
  @file.close
end

#set_sig_edit_flagObject



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/sup/modes/edit_message_mode.rb', line 223

def set_sig_edit_flag
  sig = sig_lines.join("\n")
  if $config[:edit_signature]
    pbody = @body.map { |x| x.fix_encoding! }.join("\n").fix_encoding!
    blen = pbody.length
    slen = sig.length

    if blen > slen and pbody[blen-slen..blen] == sig
      @sig_edited = false
      @body = pbody[0..blen-slen].fix_encoding!.split("\n")
    else
      @sig_edited = true
    end
  end
end

#unsaved?Boolean

Returns:

  • (Boolean)


339
# File 'lib/sup/modes/edit_message_mode.rb', line 339

def unsaved?; edited? end