sup 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sup might be problematic. Click here for more details.

Files changed (52) hide show
  1. data/HACKING +31 -9
  2. data/History.txt +7 -0
  3. data/Manifest.txt +2 -0
  4. data/Rakefile +9 -5
  5. data/bin/sup +81 -57
  6. data/bin/sup-config +1 -1
  7. data/bin/sup-sync +3 -0
  8. data/bin/sup-tweak-labels +127 -0
  9. data/doc/TODO +23 -12
  10. data/lib/sup.rb +13 -11
  11. data/lib/sup/account.rb +25 -12
  12. data/lib/sup/buffer.rb +61 -41
  13. data/lib/sup/colormap.rb +2 -0
  14. data/lib/sup/contact.rb +28 -18
  15. data/lib/sup/crypto.rb +86 -31
  16. data/lib/sup/draft.rb +12 -6
  17. data/lib/sup/horizontal-selector.rb +47 -0
  18. data/lib/sup/imap.rb +50 -37
  19. data/lib/sup/index.rb +76 -13
  20. data/lib/sup/keymap.rb +27 -8
  21. data/lib/sup/maildir.rb +1 -1
  22. data/lib/sup/mbox/loader.rb +1 -1
  23. data/lib/sup/message-chunks.rb +43 -15
  24. data/lib/sup/message.rb +67 -31
  25. data/lib/sup/mode.rb +40 -9
  26. data/lib/sup/modes/completion-mode.rb +1 -1
  27. data/lib/sup/modes/compose-mode.rb +3 -3
  28. data/lib/sup/modes/contact-list-mode.rb +12 -8
  29. data/lib/sup/modes/edit-message-mode.rb +100 -36
  30. data/lib/sup/modes/file-browser-mode.rb +1 -0
  31. data/lib/sup/modes/forward-mode.rb +43 -8
  32. data/lib/sup/modes/inbox-mode.rb +8 -5
  33. data/lib/sup/modes/label-search-results-mode.rb +12 -1
  34. data/lib/sup/modes/line-cursor-mode.rb +4 -7
  35. data/lib/sup/modes/reply-mode.rb +59 -54
  36. data/lib/sup/modes/resume-mode.rb +6 -6
  37. data/lib/sup/modes/scroll-mode.rb +4 -3
  38. data/lib/sup/modes/search-results-mode.rb +8 -5
  39. data/lib/sup/modes/text-mode.rb +19 -2
  40. data/lib/sup/modes/thread-index-mode.rb +109 -40
  41. data/lib/sup/modes/thread-view-mode.rb +180 -49
  42. data/lib/sup/person.rb +3 -3
  43. data/lib/sup/poll.rb +9 -8
  44. data/lib/sup/rfc2047.rb +7 -1
  45. data/lib/sup/sent.rb +1 -1
  46. data/lib/sup/tagger.rb +10 -4
  47. data/lib/sup/textfield.rb +7 -7
  48. data/lib/sup/thread.rb +86 -49
  49. data/lib/sup/update.rb +11 -0
  50. data/lib/sup/util.rb +74 -34
  51. data/test/test_message.rb +441 -0
  52. metadata +136 -117
data/HACKING CHANGED
@@ -1,20 +1,42 @@
1
- Running Sup locally
2
- -------------------
1
+ Running Sup from your git checkout
2
+ ----------------------------------
3
+
3
4
  Invoke it like this:
4
5
 
5
- ruby -I lib -w bin/sup
6
+ ruby -I lib -w bin/sup
6
7
 
7
8
  You'll have to install all gems mentioned in the Rakefile (look for the line
8
9
  setting p.extra_deps). If you're on a Debian or Debian-based system (e.g.
9
10
  Ubuntu), you'll have to make sure you have a complete Ruby installation,
10
- especially libssl-ruby.
11
+ especially libssl-ruby. You will need libruby-devel, gcc, and make installed
12
+ to build certain gems like Ferret. Gem install does not do a good job of
13
+ detecting when these things are missing and the build fails.
14
+
15
+ Rubygems also is particularly aggressive about picking up libraries from
16
+ installed gems. If you do have Sup installed as a gem, please examine
17
+ backtraces to make sure you're loading files from the repository and NOT from
18
+ the installed gem before submitting any bug reports.
11
19
 
12
20
  Coding standards
13
21
  ----------------
14
22
 
15
- - Don't wrap code unless it really benefits from it. The days of
16
- 80-column displays are long over. But do wrap comments and other
17
- text at whatever Emacs meta-Q does.
18
- - I like poetry mode.
23
+ - Don't wrap code unless it really benefits from it.
24
+ - Do wrap comments at 72 characters.
25
+ - Old lisp-style comment differentiations:
26
+ # one for comments on the same line as a line of code
27
+ ## two for comments on their own line, except:
28
+ ### three for comments that demarcate large sections of code (rare)
19
29
  - Use {} for one-liner blocks and do/end for multi-line blocks.
20
-
30
+ - I like poetry mode. Don't use parentheses unless you must.
31
+ - The one exception to poetry mode is if-statements that have an assignment in
32
+ the condition. To make it clear this is not a comparison, surround the
33
+ condition by parentheses. E.g.:
34
+ if a == b if(a = some.computation)
35
+ ... BUT ... something with a
36
+ end end
37
+ - and/or versus ||/&&. In Ruby, "and" and "or" bind very loosely---even
38
+ more loosely than function application. This makes them ideal for
39
+ end-of-line short-circuit control in poetry mode. So, use || and &&
40
+ for ordinary logical comparisons, and "and" and "or" for end-of-line
41
+ flow control. E.g.:
42
+ x = a || b or raise "neither is true"
@@ -1,3 +1,10 @@
1
+ == 0.4 / 2008-01-23
2
+ * GPG support for signing and encrypting outgoing mail
3
+ * New hooks: mime attachment, attribution line
4
+ * Improved local charset detection using gettext library
5
+ * Better quoted region detection
6
+ * Many bugfixes and UI improvements
7
+
1
8
  == 0.3 / 2007-10-29
2
9
  * In-buffer search (finally!)
3
10
  * Subscribe to/unsubscribe from mailing list commands.
@@ -11,6 +11,7 @@ bin/sup-dump
11
11
  bin/sup-recover-sources
12
12
  bin/sup-sync
13
13
  bin/sup-sync-back
14
+ bin/sup-tweak-labels
14
15
  doc/FAQ.txt
15
16
  doc/Hooks.txt
16
17
  doc/NewUserGuide.txt
@@ -24,6 +25,7 @@ lib/sup/contact.rb
24
25
  lib/sup/crypto.rb
25
26
  lib/sup/draft.rb
26
27
  lib/sup/hook.rb
28
+ lib/sup/horizontal-selector.rb
27
29
  lib/sup/imap.rb
28
30
  lib/sup/index.rb
29
31
  lib/sup/keymap.rb
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- # -*- ruby -*-
2
-
3
1
  require 'rubygems'
4
2
  require 'hoe'
5
3
  $:.unshift 'lib' # force loading from ./lib/ if it exists
@@ -9,7 +7,12 @@ class Hoe
9
7
  def extra_deps; @extra_deps.reject { |x| Array(x).first == "hoe" } end
10
8
  end # thanks to "Mike H"
11
9
 
12
- Hoe.new('sup', Redwood::VERSION) do |p|
10
+ ## allow people who use development versions by running "rake gem"
11
+ ## and installing the resulting gem it to be able to do this. (gem
12
+ ## versions must be in dotted-digit notation only).
13
+ version = Redwood::VERSION == "git" ? "999" : Redwood::VERSION
14
+
15
+ Hoe.new('sup', version) do |p|
13
16
  p.rubyforge_name = 'sup'
14
17
  p.author = "William Morgan"
15
18
  p.summary = 'A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.'
@@ -17,7 +20,7 @@ Hoe.new('sup', Redwood::VERSION) do |p|
17
20
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2].gsub(/^\s+/, "")
18
21
  p.changes = p.paragraphs_of('History.txt', 0..0).join("\n\n")
19
22
  p.email = "wmorgan-sup@masanjin.net"
20
- p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh', ['trollop', '>= 1.7'], 'lockfile', 'mime-types']
23
+ p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh', ['trollop', '>= 1.7'], 'lockfile', 'mime-types', 'gettext']
21
24
  end
22
25
 
23
26
  rule 'ss?.png' => 'ss?-small.png' do |t|
@@ -45,4 +48,5 @@ task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
45
48
  sh "scp -C #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
46
49
  end
47
50
 
48
- # vim: syntax=Ruby
51
+ # vim: syntax=ruby
52
+ # -*- ruby -*-
data/bin/sup CHANGED
@@ -7,6 +7,20 @@ require 'fileutils'
7
7
  require 'trollop'
8
8
  require "sup"
9
9
 
10
+ BIN_VERSION = "0.4"
11
+
12
+ unless Redwood::VERSION == BIN_VERSION
13
+ $stderr.puts <<EOS
14
+
15
+ Error: version mismatch!
16
+ The sup executable is at version #{BIN_VERSION.inspect}.
17
+ The sup libraries are at version #{Redwood::VERSION.inspect}.
18
+
19
+ Is your development environment conflicting with rubygems?
20
+ EOS
21
+ exit(-1)
22
+ end
23
+
10
24
  $exceptions = []
11
25
  $opts = Trollop::options do
12
26
  version "sup v#{Redwood::VERSION}"
@@ -20,6 +34,7 @@ Options are:
20
34
  EOS
21
35
  opt :list_hooks, "List all hooks and descriptions thereof, and quit."
22
36
  opt :no_threads, "Turn of threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
37
+ opt :no_initial_poll, "Don't poll for new messages when starting."
23
38
  opt :search, "Search for threads ", :type => String
24
39
  end
25
40
 
@@ -154,9 +169,8 @@ begin
154
169
  Ncurses::A_BOLD
155
170
  c.add :completion_character_color, Ncurses::COLOR_WHITE,
156
171
  Ncurses::COLOR_BLACK, Ncurses::A_BOLD
157
- c.add :reply_mode_selected_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
158
- c.add :reply_mode_unselected_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
159
- c.add :reply_mode_label_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
172
+ c.add :horizontal_selector_selected_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
173
+ c.add :horizontal_selector_unselected_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
160
174
  c.add :search_highlight_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_YELLOW, Ncurses::A_BOLD, :highlight => :search_highlight_color
161
175
  end
162
176
 
@@ -180,9 +194,9 @@ begin
180
194
  Redwood::log "fatal error loading from #{s}: #{e.message}"
181
195
  end
182
196
  end
183
- end
197
+ end unless $opts[:no_initial_poll]
184
198
 
185
- imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] }
199
+ imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
186
200
 
187
201
  unless $opts[:no_threads]
188
202
  PollManager.start
@@ -199,67 +213,77 @@ begin
199
213
  next unless c
200
214
  bm.erase_flash
201
215
 
202
- unless bm.handle_input(c)
203
- x = global_keymap.action_for c
204
- case x
205
- when :quit
206
- break if bm.kill_all_buffers_safely
207
- when :help
208
- curmode = bm.focus_buf.mode
209
- bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
210
- when :roll_buffers
211
- bm.roll_buffers
212
- when :roll_buffers_backwards
213
- bm.roll_buffers_backwards
214
- when :kill_buffer
215
- bm.kill_buffer_safely bm.focus_buf
216
- when :list_buffers
217
- bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
218
- when :list_contacts
219
- b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
220
- b.mode.load_in_background if new
221
- when :search
222
- query = BufferManager.ask :search, "search all messages: "
223
- next unless query && query !~ /^\s*$/
224
- SearchResultsMode.spawn_from_query query
225
- when :list_labels
226
- labels = LabelManager.listable_labels.map { |l| LabelManager.string_for l }
227
- user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
228
- unless user_label.nil?
229
- if user_label.empty?
230
- bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty?
231
- else
232
- LabelSearchResultsMode.spawn_nicely user_label
233
- end
216
+ action =
217
+ begin
218
+ if bm.handle_input c
219
+ :nothing
220
+ else
221
+ bm.resolve_input_with_keymap c, global_keymap
234
222
  end
235
- when :compose
236
- ComposeMode.spawn_nicely
237
- when :poll
238
- reporting_thread("user-invoked poll") { PollManager.poll }
239
- when :recall_draft
240
- case Index.num_results_for :label => :draft
241
- when 0
242
- bm.flash "No draft messages."
243
- when 1
244
- m = nil
245
- Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
246
- r = ResumeMode.new(m)
247
- BufferManager.spawn "Edit message", r
248
- r.edit_message
223
+ rescue InputSequenceAborted
224
+ :nothing
225
+ end
226
+
227
+ case action
228
+ when :quit
229
+ break if bm.kill_all_buffers_safely
230
+ when :help
231
+ curmode = bm.focus_buf.mode
232
+ bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
233
+ when :roll_buffers
234
+ bm.roll_buffers
235
+ when :roll_buffers_backwards
236
+ bm.roll_buffers_backwards
237
+ when :kill_buffer
238
+ bm.kill_buffer_safely bm.focus_buf
239
+ when :list_buffers
240
+ bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
241
+ when :list_contacts
242
+ b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
243
+ b.mode.load_in_background if new
244
+ when :search
245
+ query = BufferManager.ask :search, "search all messages: "
246
+ next unless query && query !~ /^\s*$/
247
+ SearchResultsMode.spawn_from_query query
248
+ when :list_labels
249
+ labels = LabelManager.listable_labels.map { |l| LabelManager.string_for l }
250
+ user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
251
+ unless user_label.nil?
252
+ if user_label.empty?
253
+ bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty?
249
254
  else
250
- b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
251
- b.mode.load_threads :num => b.content_height if new
255
+ LabelSearchResultsMode.spawn_nicely user_label
252
256
  end
253
- when :nothing
254
- when :redraw
255
- bm.completely_redraw_screen
257
+ end
258
+ when :compose
259
+ ComposeMode.spawn_nicely
260
+ when :poll
261
+ reporting_thread("user-invoked poll") { PollManager.poll }
262
+ when :recall_draft
263
+ case Index.num_results_for :label => :draft
264
+ when 0
265
+ bm.flash "No draft messages."
266
+ when 1
267
+ m = nil
268
+ Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
269
+ r = ResumeMode.new(m)
270
+ BufferManager.spawn "Edit message", r
271
+ r.edit_message
256
272
  else
257
- bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
273
+ b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
274
+ b.mode.load_threads :num => b.content_height if new
258
275
  end
276
+ when :nothing, InputSequenceAborted
277
+ when :redraw
278
+ bm.completely_redraw_screen
279
+ else
280
+ bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
259
281
  end
260
282
 
261
283
  bm.draw_screen
262
284
  end
285
+
286
+ bm.kill_all_buffers if SuicideManager.die?
263
287
  rescue Exception => e
264
288
  $exceptions << [e, "main"]
265
289
  ensure
@@ -96,7 +96,7 @@ def add_source
96
96
  $last_folder = fn
97
97
 
98
98
  fn = "/#{fn}" # lame
99
- if srv =~ /^(\w+):(\d+)$/
99
+ if srv =~ /^(\S+):(\d+)$/
100
100
  host, port = $1, $2.to_i
101
101
  else
102
102
  host, port = srv, nil
@@ -7,6 +7,9 @@ require "sup"
7
7
 
8
8
  class Float
9
9
  def to_s; sprintf '%.2f', self; end
10
+ def to_time_s
11
+ infinite? ? "unknown" : super
12
+ end
10
13
  end
11
14
 
12
15
  class Numeric
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+ require "sup"
6
+
7
+ class Float
8
+ def to_s; sprintf '%.2f', self; end
9
+ def to_time_s
10
+ infinite? ? "unknown" : super
11
+ end
12
+ end
13
+
14
+ class Numeric
15
+ def to_time_s
16
+ i = to_i
17
+ sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
18
+ end
19
+ end
20
+
21
+ def time
22
+ startt = Time.now
23
+ yield
24
+ Time.now - startt
25
+ end
26
+
27
+ opts = Trollop::options do
28
+ version "sup-tweak-labels (sup #{Redwood::VERSION})"
29
+ banner <<EOS
30
+ Batch modification of message state for messages already in the index.
31
+
32
+ Usage:
33
+ sup-tweak-labels [options] <source>*
34
+
35
+ where <source>* is zero or more source URIs. Supported source URI schemes can
36
+ be seen by running "sup-add --help".
37
+
38
+ Options:
39
+ EOS
40
+ opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :type => String
41
+ opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :type => String
42
+
43
+ text <<EOS
44
+
45
+ Other options:
46
+ EOS
47
+ opt :verbose, "Print message ids as they're processed."
48
+ opt :all_sources, "Scan over all sources.", :short => :none
49
+ opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
50
+ opt :version, "Show version information", :short => :none
51
+ end
52
+
53
+ add_labels = (opts[:add] || "").split(",").map { |l| l.intern }.uniq
54
+ remove_labels = (opts[:remove] || "").split(",").map { |l| l.intern }.uniq
55
+
56
+ Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
57
+
58
+ Redwood::start
59
+ begin
60
+ index = Redwood::Index.new
61
+ index.load
62
+
63
+ source_ids =
64
+ if opts[:all_sources]
65
+ index.sources
66
+ else
67
+ ARGV.map do |uri|
68
+ index.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
69
+ end
70
+ end.map { |s| s.id }
71
+ Trollop::die "nothing to do: no sources" if source_ids.empty?
72
+
73
+ query = "+(" + source_ids.map { |id| "source_id:#{id}" }.join(" ") + ")"
74
+ if add_labels.empty?
75
+ ## if all we're doing is removing labels, we can further restrict the
76
+ ## query to only messages with those labels
77
+ query += " +(" + remove_labels.map { |l| "label:#{l}" }.join(" ") + ")"
78
+ end
79
+
80
+ results = index.ferret.search query, :limit => :all
81
+ num_total = results.total_hits
82
+
83
+ $stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."
84
+
85
+ num_changed = num_scanned = 0
86
+ last_info_time = start_time = Time.now
87
+ results.hits.each do |hit|
88
+ num_scanned += 1
89
+ id = hit.doc
90
+
91
+ m = index.build_message id
92
+ old_labels = m.labels.clone
93
+
94
+ m.labels += add_labels
95
+ m.labels -= remove_labels
96
+ m.labels = m.labels.uniq
97
+
98
+ unless m.labels.sort_by { |s| s.to_s } == old_labels.sort_by { |s| s.to_s }
99
+ num_changed += 1
100
+ puts "#{m.id}: {#{old_labels.join ','}} => {#{m.labels.join ','}}" if opts[:verbose]
101
+ index.sync_message m unless opts[:dry_run]
102
+ end
103
+
104
+ if Time.now - last_info_time > 60
105
+ last_info_time = Time.now
106
+ elapsed = last_info_time - start_time
107
+ pctdone = 100.0 * num_scanned.to_f / num_total.to_f
108
+ remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
109
+ $stderr.puts "## #{num_scanned} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining"
110
+ end
111
+ end
112
+ $stderr.puts "Scanned #{num_scanned} / #{num_total} messages and changed #{num_changed}."
113
+
114
+ unless num_changed == 0
115
+ $stderr.puts "Optimizing index..."
116
+ index.ferret.optimize unless opts[:dry_run]
117
+ end
118
+
119
+ rescue Exception => e
120
+ File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
121
+ raise
122
+ ensure
123
+ index.save
124
+ Redwood::finish
125
+ index.unlock
126
+ end
127
+
data/doc/TODO CHANGED
@@ -1,20 +1,10 @@
1
- for 0.4
1
+ for 0.5
2
2
  -------
3
3
  _ mark thread as unread should remember the unread messages and mark
4
4
  only them as unread, just like gmail
5
- _ mark thread as unread should have a version within thread-view-mode
6
- which then also closes the buffer
7
5
  _ bugfix: time zone parsing broken?
8
- _ forwards optionally include attachments
9
- _ attach messages
10
- _ flesh out gpg integration: sign & encrypt outgoing
11
- _ mbox: don't keep filehandles open, and protect all reads with
12
- dotlockfile
13
- _ imap: share connection to the same server.
14
- _ bugfix: screwing with the headers when editing causes a crash
15
6
  _ need a better way to force an address to a particular name,
16
7
  for things like evite addresses
17
- _ pressing A in thread-view-mode should jump to next message
18
8
  _ imap "add all folders on this server" option in sup-add
19
9
  _ for new message flashes, add new message counts until keypress
20
10
  _ bugfix: missing sources should be handled better
@@ -22,13 +12,29 @@ _ search results: highlight relevant snippets and open to relevant
22
12
  portion of thread
23
13
  _ have "notes" (treated as emails to oneself, never sent) as
24
14
  first-class objects.
15
+ _ make use of the username in URIs, and move account passwords to
16
+ a different file from sources.yaml. heck, allow encryption of that
17
+ file.
18
+
19
+ for 0.4
20
+ -------
21
+ _ bugfix in drafts: the entire thread is currently discarded, rather
22
+ than just the one message. (need to distinguish per-message and
23
+ per-thread deletion in the update messages.)
24
+ _ bugfix in completion: capitalization for contact names
25
+ _ imap: cache headers
26
+ _ imap: share connection to the same server.
27
+ _ dispatch-and-kill version of mark thread as unread in thread-view-mode
28
+ _ forward for one or more tagged messages should attach them
29
+ _ mbox: don't keep filehandles open, and protect all reads with dotlockfile
30
+ _ bugfix: screwing with the headers when editing causes a crash
25
31
 
26
32
  future
27
33
  ------
28
34
  _ ldbd support
29
35
  _ don't use a people.txt; store email addresses directly in the index. too many
30
36
  problems with email addresses that occur with multiple names.
31
- _ infix match instead of prefix match for tab completion (maybe!)
37
+ _ infix match instead of prefix match for tab completion
32
38
  _ fix killed threads contributing to unread message count problem (prob. need
33
39
  to maintain all killed message ids and our own unread message count for
34
40
  inbox).
@@ -77,6 +83,11 @@ x gmail support: obsoleted by imap
77
83
 
78
84
  done
79
85
  ----
86
+ x bugfix: threading broken
87
+ x bugfix: thread ordering in thread-index-mode sometimes jumps around with 'M'
88
+ x forwards optionally include attachments
89
+ x flesh out gpg integration: sign & encrypt outgoing
90
+ x pressing A in thread-view-mode should jump to next message
80
91
  x multi-thread dump upon crash
81
92
  x hook manager caches values of any proc "variables"
82
93
  x bugfix: remove delay on startup if a usual imap source exists