sup 1.3 → 1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/checks.yml +10 -9
- data/.rubocop.yml +1 -1
- data/History.txt +17 -0
- data/Manifest.txt +9 -1
- data/README.md +5 -3
- data/Rakefile +1 -1
- data/bin/sup +5 -3
- data/contrib/nix/Gemfile.lock +39 -32
- data/contrib/nix/gemset.nix +80 -58
- data/contrib/nix/ruby2.4-Gemfile.lock +1 -1
- data/contrib/nix/ruby2.4-gemset.nix +2 -2
- data/contrib/nix/ruby2.5-Gemfile.lock +1 -1
- data/contrib/nix/ruby2.5-gemset.nix +2 -2
- data/contrib/nix/ruby2.6-Gemfile.lock +1 -1
- data/contrib/nix/ruby2.6-gemset.nix +2 -2
- data/contrib/nix/ruby2.7-Gemfile.lock +1 -1
- data/contrib/nix/ruby2.7-gemset.nix +2 -2
- data/contrib/nix/ruby3.0-Gemfile.lock +1 -1
- data/contrib/nix/ruby3.0-gemset.nix +2 -2
- data/contrib/nix/ruby3.1-Gemfile.lock +101 -0
- data/contrib/nix/ruby3.1-gemset.nix +391 -0
- data/contrib/nix/ruby3.1-shell.nix +2 -13
- data/contrib/nix/ruby3.2-Gemfile.lock +101 -0
- data/contrib/nix/ruby3.2-gemset.nix +391 -0
- data/contrib/nix/ruby3.2-shell.nix +2 -13
- data/contrib/nix/ruby3.3-shell.nix +1 -12
- data/contrib/nix/ruby3.4-shell.nix +1 -10
- data/contrib/nix/ruby4.0-shell.nix +40 -0
- data/lib/sup/account.rb +2 -0
- data/lib/sup/buffer.rb +1 -0
- data/lib/sup/contact.rb +3 -4
- data/lib/sup/draft.rb +15 -11
- data/lib/sup/index.rb +8 -4
- data/lib/sup/message_chunks.rb +0 -24
- data/lib/sup/mode.rb +1 -0
- data/lib/sup/modes/contact_list_mode.rb +0 -1
- data/lib/sup/modes/edit_message_mode.rb +1 -1
- data/lib/sup/modes/label_search_results_mode.rb +1 -2
- data/lib/sup/modes/line_cursor_mode.rb +22 -20
- data/lib/sup/modes/search_results_mode.rb +0 -1
- data/lib/sup/modes/thread_view_mode.rb +1 -2
- data/lib/sup/rfc2047.rb +5 -2
- data/lib/sup/source.rb +2 -0
- data/lib/sup/version.rb +1 -1
- data/lib/sup.rb +1 -1
- data/man/sup-add.1 +6 -6
- data/man/sup-config.1 +6 -6
- data/man/sup-dump.1 +5 -5
- data/man/sup-import-dump.1 +7 -7
- data/man/sup-recover-sources.1 +5 -5
- data/man/sup-sync-back-maildir.1 +9 -9
- data/man/sup-sync.1 +11 -11
- data/man/sup-tweak-labels.1 +10 -10
- data/man/sup.1 +11 -11
- data/sup.gemspec +1 -1
- data/test/dummy_buffer.rb +34 -0
- data/test/fixtures/contacts.txt +2 -1
- data/test/fixtures/rfc2047-header-encoding.eml +1 -1
- data/test/integration/test_draft.rb +128 -0
- data/test/integration/test_maildir.rb +2 -0
- data/test/integration/test_mbox.rb +2 -0
- data/test/integration/test_sup-add.rb +4 -0
- data/test/test_crypto.rb +7 -2
- data/test/test_message.rb +1 -0
- data/test/unit/test_contact.rb +15 -3
- data/test/unit/test_edit_message_mode.rb +6 -1
- data/test/unit/test_index.rb +65 -0
- data/test/unit/test_line_cursor_mode.rb +208 -0
- metadata +17 -7
- data/shell.nix +0 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
require "sup"
|
|
2
|
+
require "test_helper"
|
|
3
|
+
|
|
4
|
+
class TestDraft < Minitest::Test
|
|
5
|
+
include Redwood
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
@path = Dir.mktmpdir
|
|
9
|
+
start
|
|
10
|
+
Logger.remove_sink $stderr
|
|
11
|
+
@draft_dir = File.join @path, "drafts"
|
|
12
|
+
@test_message_1 = <<EOS
|
|
13
|
+
From: Some Person <someone@example.invalid>
|
|
14
|
+
To:
|
|
15
|
+
Cc:
|
|
16
|
+
Bcc:
|
|
17
|
+
Subject: draft
|
|
18
|
+
Date: Fri, 11 Apr 2025 22:34:05 +1000
|
|
19
|
+
Message-ID: <123@example.invalid>
|
|
20
|
+
|
|
21
|
+
My incomplete message
|
|
22
|
+
EOS
|
|
23
|
+
DraftManager.instance.instance_eval "@dir = '#{@draft_dir}'"
|
|
24
|
+
Index.init @path
|
|
25
|
+
Index.load
|
|
26
|
+
SourceManager.instance.instance_eval "@sources = {}"
|
|
27
|
+
@draft_source = DraftManager.new_source
|
|
28
|
+
SourceManager.add_source @draft_source
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def teardown
|
|
32
|
+
Index.save_index
|
|
33
|
+
ObjectSpace.each_object(Class).select {|a| a < Redwood::Singleton}.each do |klass|
|
|
34
|
+
klass.deinstantiate! unless klass == Redwood::Logger
|
|
35
|
+
end
|
|
36
|
+
FileUtils.rm_r @path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_write_draft
|
|
40
|
+
DraftManager.write_draft { |f| f.write @test_message_1 }
|
|
41
|
+
|
|
42
|
+
draft_filename = File.join @draft_dir, "0"
|
|
43
|
+
assert File.exist? draft_filename
|
|
44
|
+
assert_equal @test_message_1, (File.read draft_filename)
|
|
45
|
+
|
|
46
|
+
## Check that it is loaded back into the index successfully too.
|
|
47
|
+
messages_in_index = Index.instance.enum_for(:each_message).to_a
|
|
48
|
+
assert_equal @test_message_1, messages_in_index.first.raw_message
|
|
49
|
+
assert_equal [:draft, :inbox].to_set, messages_in_index.first.labels
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_discard_draft
|
|
53
|
+
DraftManager.write_draft { |f| f.write @test_message_1 }
|
|
54
|
+
draft_filename = File.join @draft_dir, "0"
|
|
55
|
+
assert File.exist? draft_filename
|
|
56
|
+
message_in_index = Index.instance.enum_for(:each_message).to_a.first
|
|
57
|
+
|
|
58
|
+
DraftManager.discard message_in_index
|
|
59
|
+
refute File.exist? draft_filename
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_discard_already_deleted_from_disk
|
|
63
|
+
DraftManager.write_draft { |f| f.write @test_message_1 }
|
|
64
|
+
draft_filename = File.join @draft_dir, "0"
|
|
65
|
+
assert File.exist? draft_filename
|
|
66
|
+
message_in_index = Index.instance.enum_for(:each_message).to_a.first
|
|
67
|
+
|
|
68
|
+
File.delete draft_filename
|
|
69
|
+
|
|
70
|
+
DraftManager.discard message_in_index
|
|
71
|
+
refute File.exist? draft_filename
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_load_malformed_draft
|
|
75
|
+
## Sup always writes drafts by serialising a Message, meaning the draft is
|
|
76
|
+
## guaranteed to have certain headers like Date. But it's always possible
|
|
77
|
+
## for the user to edit the draft directly on the filesystem and leave it
|
|
78
|
+
## in some kind of malformed state. Sup should handle it without crashing.
|
|
79
|
+
draft_filename = File.join @draft_dir, "0"
|
|
80
|
+
fallback_date = Time.new 2025, 5, 3, 15, 47, 41
|
|
81
|
+
File.write draft_filename, <<EOS
|
|
82
|
+
Some-Header: Value
|
|
83
|
+
|
|
84
|
+
body
|
|
85
|
+
EOS
|
|
86
|
+
File.utime fallback_date, fallback_date, draft_filename
|
|
87
|
+
PollManager.poll_from @draft_source
|
|
88
|
+
messages_in_index = Index.instance.enum_for(:each_message).to_a
|
|
89
|
+
assert_equal "", messages_in_index[0].subj
|
|
90
|
+
assert_equal fallback_date, messages_in_index[0].date
|
|
91
|
+
|
|
92
|
+
File.write (File.join @draft_dir, "1"), <<EOS
|
|
93
|
+
missing a header!
|
|
94
|
+
EOS
|
|
95
|
+
PollManager.poll_from @draft_source
|
|
96
|
+
messages_in_index = Index.instance.enum_for(:each_message).to_a
|
|
97
|
+
assert_equal "", messages_in_index[0].subj
|
|
98
|
+
|
|
99
|
+
File.write (File.join @draft_dir, "2"), ""
|
|
100
|
+
PollManager.poll_from @draft_source
|
|
101
|
+
messages_in_index = Index.instance.enum_for(:each_message).to_a
|
|
102
|
+
assert_equal "", messages_in_index[0].subj
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_draft_with_non_ascii_chars
|
|
106
|
+
message = <<EOS
|
|
107
|
+
From: Some Person <someone@example.invalid>
|
|
108
|
+
To:
|
|
109
|
+
Cc:
|
|
110
|
+
Bcc:
|
|
111
|
+
Subject: UTF-8 draft 🤐
|
|
112
|
+
Date: Fri, 11 Apr 2025 22:34:05 +1000
|
|
113
|
+
Message-ID: <123@example.invalid>
|
|
114
|
+
|
|
115
|
+
¡Buen día! Доброго ранку! おはよう!
|
|
116
|
+
EOS
|
|
117
|
+
DraftManager.write_draft { |f| f.write message }
|
|
118
|
+
draft_filename = File.join @draft_dir, "0"
|
|
119
|
+
assert_equal message, (File.read draft_filename)
|
|
120
|
+
|
|
121
|
+
PollManager.poll_from @draft_source
|
|
122
|
+
messages_in_index = Index.instance.enum_for(:each_message).to_a
|
|
123
|
+
assert_equal "UTF-8 draft 🤐", messages_in_index[0].subj
|
|
124
|
+
assert_equal message, messages_in_index.first.raw_message
|
|
125
|
+
assert_equal "¡Buen día! Доброго ранку! おはよう!", \
|
|
126
|
+
messages_in_index[0].chunks[0].lines[0]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -41,11 +41,13 @@ EOS
|
|
|
41
41
|
|
|
42
42
|
def start_sup_and_add_source(source)
|
|
43
43
|
start
|
|
44
|
+
Logger.remove_sink $stderr
|
|
44
45
|
Index.init @path
|
|
45
46
|
Index.load
|
|
46
47
|
SourceManager.instance.instance_eval '@sources = {}'
|
|
47
48
|
SourceManager.instance.add_source source
|
|
48
49
|
PollManager.poll_from source
|
|
50
|
+
Index.save_index
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
# and now, let the tests begin!
|
|
@@ -34,11 +34,13 @@ EOS
|
|
|
34
34
|
|
|
35
35
|
def start_sup_and_add_source(source)
|
|
36
36
|
start
|
|
37
|
+
Logger.remove_sink $stderr
|
|
37
38
|
Index.init @path
|
|
38
39
|
Index.load
|
|
39
40
|
SourceManager.instance.instance_eval '@sources = {}'
|
|
40
41
|
SourceManager.instance.add_source source
|
|
41
42
|
PollManager.poll_from source
|
|
43
|
+
Index.save_index
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
# and now, let the tests begin!
|
|
@@ -26,6 +26,8 @@ class TestSupAdd < Minitest::Test
|
|
|
26
26
|
EOS
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
## https://github.com/sup-heliotrope/sup/issues/577
|
|
30
|
+
## https://github.com/sup-heliotrope/sup/issues/516
|
|
29
31
|
def test_fixes_old_tag_uri_syntax
|
|
30
32
|
File.write "#{@path}/sources.yaml", <<EOS
|
|
31
33
|
---
|
|
@@ -36,6 +38,7 @@ EOS
|
|
|
36
38
|
sync_back: true
|
|
37
39
|
id: 1
|
|
38
40
|
labels: []
|
|
41
|
+
- !supmua.org,2006-10-01/Redwood/SentLoader {}
|
|
39
42
|
EOS
|
|
40
43
|
_out, _err = capture_subprocess_io do
|
|
41
44
|
assert system({"SUP_BASE" => @path}, "bin/sup-add", "maildir:///other/path")
|
|
@@ -58,6 +61,7 @@ EOS
|
|
|
58
61
|
sync_back: true
|
|
59
62
|
id: 2
|
|
60
63
|
labels: []
|
|
64
|
+
- !<tag:supmua.org,2006-10-01/Redwood/SentLoader> {}
|
|
61
65
|
EOS
|
|
62
66
|
end
|
|
63
67
|
|
data/test/test_crypto.rb
CHANGED
|
@@ -38,8 +38,13 @@ class TestCryptoManager < Minitest::Test
|
|
|
38
38
|
@path = Dir.mktmpdir
|
|
39
39
|
Redwood::HookManager.init File.join(@path, 'hooks')
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
account = {
|
|
42
|
+
:name => +"test",
|
|
43
|
+
:email => @from_email.dup,
|
|
44
|
+
:alternates => [@from_email_ecc.dup],
|
|
45
|
+
:sendmail => "/bin/false",
|
|
46
|
+
}
|
|
47
|
+
Redwood::AccountManager.init :default => account
|
|
43
48
|
|
|
44
49
|
Redwood::CryptoManager.init
|
|
45
50
|
end
|
data/test/test_message.rb
CHANGED
|
@@ -253,6 +253,7 @@ class TestMessage < Minitest::Test
|
|
|
253
253
|
"bad: =?UTF16?q?badcharsetname?==?US-ASCII?b?/w?=" +
|
|
254
254
|
"=?UTF-7?Q?=41=6D=65=72=69=63=61=E2=80=99=73?=",
|
|
255
255
|
sup_message.subj)
|
|
256
|
+
assert_equal "=?utf-8?q?YouTube-tj=E4nst?=", sup_message.from.name
|
|
256
257
|
end
|
|
257
258
|
|
|
258
259
|
def test_nonascii_header
|
data/test/unit/test_contact.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
|
+
require 'sup'
|
|
2
3
|
require 'sup/contact'
|
|
3
4
|
|
|
4
5
|
module Redwood
|
|
@@ -16,9 +17,20 @@ class TestContact < Minitest::Test
|
|
|
16
17
|
|
|
17
18
|
def test_contact_manager
|
|
18
19
|
assert @contact
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
assert_equal @contact.
|
|
20
|
+
|
|
21
|
+
## 2 contacts are imported from the fixture file.
|
|
22
|
+
assert_equal 2, @contact.contacts.count
|
|
23
|
+
|
|
24
|
+
rc = @contact.contact_for "RC"
|
|
25
|
+
assert_equal "Random Contact", rc.name
|
|
26
|
+
assert @contact.is_aliased_contact? rc
|
|
27
|
+
assert_equal "RC", @contact.alias_for(rc)
|
|
28
|
+
|
|
29
|
+
uc = @contact.person_for "unaliased@example.invalid"
|
|
30
|
+
refute @contact.is_aliased_contact? uc
|
|
31
|
+
assert_nil @contact.alias_for uc
|
|
32
|
+
assert_equal [rc, uc], @contact.contacts
|
|
33
|
+
assert_equal [rc], @contact.contacts_with_aliases
|
|
22
34
|
|
|
23
35
|
assert_nil @contact.contact_for "TN"
|
|
24
36
|
@contact.update_alias @person, "TN"
|
|
@@ -24,7 +24,12 @@ class TestEditMessageMode < Minitest::Test
|
|
|
24
24
|
$config = {}
|
|
25
25
|
@path = Dir.mktmpdir
|
|
26
26
|
Redwood::HookManager.init File.join(@path, "hooks")
|
|
27
|
-
|
|
27
|
+
account = {
|
|
28
|
+
:name => +"test",
|
|
29
|
+
:email => +"sender@example.invalid",
|
|
30
|
+
:sendmail => "/bin/false",
|
|
31
|
+
}
|
|
32
|
+
Redwood::AccountManager.init :default => account
|
|
28
33
|
Redwood::CryptoManager.instance_variable_set :@instance, DummyCryptoManager.new
|
|
29
34
|
end
|
|
30
35
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require "minitest/mock"
|
|
2
|
+
require "test_helper"
|
|
3
|
+
|
|
4
|
+
require "sup"
|
|
5
|
+
|
|
6
|
+
class TestIndex < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@path = Dir.mktmpdir
|
|
9
|
+
Redwood::start
|
|
10
|
+
Redwood::Logger.remove_sink $stderr
|
|
11
|
+
Redwood::Index.init @path
|
|
12
|
+
Redwood::Index.load
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
Redwood::Index.save_index
|
|
17
|
+
ObjectSpace.each_object(Class).select {|a| a < Redwood::Singleton}.each do |klass|
|
|
18
|
+
klass.deinstantiate! unless klass == Redwood::Logger
|
|
19
|
+
end
|
|
20
|
+
FileUtils.rm_r @path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_fake_time &block
|
|
24
|
+
Time.stub :now, Time.utc(2000) do
|
|
25
|
+
## Also stub Time.local to behave like Time.utc so that Chronic.parse
|
|
26
|
+
## doesn't pick up the timezone of the test runner.
|
|
27
|
+
Time.stub :local, ->(*args) { Time.utc(*args) }, &block
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_date_query_parsing
|
|
32
|
+
parsed = with_fake_time do
|
|
33
|
+
Redwood::Index.parse_query "after:yesterday"
|
|
34
|
+
end
|
|
35
|
+
expected_qobj = Xapian::Query.new(
|
|
36
|
+
Xapian::Query::OP_VALUE_RANGE,
|
|
37
|
+
Redwood::Index::DATE_VALUENO,
|
|
38
|
+
Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
|
|
39
|
+
Xapian.sortable_serialise(2**32),
|
|
40
|
+
)
|
|
41
|
+
assert_equal expected_qobj.description, parsed[:qobj].description
|
|
42
|
+
|
|
43
|
+
parsed = with_fake_time do
|
|
44
|
+
Redwood::Index.parse_query "before:yesterday"
|
|
45
|
+
end
|
|
46
|
+
expected_qobj = Xapian::Query.new(
|
|
47
|
+
Xapian::Query::OP_VALUE_RANGE,
|
|
48
|
+
Redwood::Index::DATE_VALUENO,
|
|
49
|
+
Xapian.sortable_serialise(0),
|
|
50
|
+
Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
|
|
51
|
+
)
|
|
52
|
+
assert_equal expected_qobj.description, parsed[:qobj].description
|
|
53
|
+
|
|
54
|
+
parsed = with_fake_time do
|
|
55
|
+
Redwood::Index.parse_query "during:yesterday"
|
|
56
|
+
end
|
|
57
|
+
expected_qobj = Xapian::Query.new(
|
|
58
|
+
Xapian::Query::OP_VALUE_RANGE,
|
|
59
|
+
Redwood::Index::DATE_VALUENO,
|
|
60
|
+
Xapian.sortable_serialise(Time.utc(1999, 12, 31).to_i),
|
|
61
|
+
Xapian.sortable_serialise(Time.utc(2000, 1, 1).to_i),
|
|
62
|
+
)
|
|
63
|
+
assert_equal expected_qobj.description, parsed[:qobj].description
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "dummy_buffer"
|
|
3
|
+
|
|
4
|
+
require "sup"
|
|
5
|
+
|
|
6
|
+
class TestLineCursorMode < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
$config = {
|
|
9
|
+
:load_more_threads_hysteresis => 0,
|
|
10
|
+
:load_more_threads_when_scrolling => true,
|
|
11
|
+
:continuous_scroll => false,
|
|
12
|
+
}
|
|
13
|
+
Redwood::BufferManager.init
|
|
14
|
+
@modes_to_cleanup = []
|
|
15
|
+
@lines = []
|
|
16
|
+
@load_more = Thread::Queue.new
|
|
17
|
+
@buffer_height = 41 # 1 for status line, 40 usable lines
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def teardown
|
|
21
|
+
@modes_to_cleanup.each { |mode| mode.cleanup }
|
|
22
|
+
Redwood::BufferManager.deinstantiate!
|
|
23
|
+
$config = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def make_mode
|
|
27
|
+
mode = Redwood::LineCursorMode.new
|
|
28
|
+
@modes_to_cleanup << mode
|
|
29
|
+
lines = @lines
|
|
30
|
+
mode.define_singleton_method(:lines) { lines.length }
|
|
31
|
+
mode.define_singleton_method(:[]) { |i| lines[i] }
|
|
32
|
+
mode.send(:to_load_more) { |n| @load_more << n }
|
|
33
|
+
mode.buffer = Redwood::DummyBuffer.new 100, @buffer_height
|
|
34
|
+
mode.spawned
|
|
35
|
+
mode.draw
|
|
36
|
+
mode
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def expect_load_more n
|
|
40
|
+
begin
|
|
41
|
+
requested = @load_more.pop :timeout => 0.1
|
|
42
|
+
rescue ThreadError
|
|
43
|
+
## Ruby < 3.2 does not obey the timeout for Queue#pop
|
|
44
|
+
sleep 0.1
|
|
45
|
+
requested = @load_more.pop true
|
|
46
|
+
end
|
|
47
|
+
refute_nil requested, "Expected load_more callbacks to fire"
|
|
48
|
+
assert_equal n, requested
|
|
49
|
+
(0...n).map { |i| @lines << "line #{i}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_cursor_down
|
|
53
|
+
mode = make_mode
|
|
54
|
+
expect_load_more 41
|
|
55
|
+
mode.draw # curpos gets messed up without this call, why?
|
|
56
|
+
|
|
57
|
+
21.times do
|
|
58
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
59
|
+
end
|
|
60
|
+
assert_equal 21, mode.curpos
|
|
61
|
+
assert_equal 0, mode.topline
|
|
62
|
+
|
|
63
|
+
## Two past halfway, load_more callbacks are triggered.
|
|
64
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
65
|
+
assert_equal 22, mode.curpos
|
|
66
|
+
expect_load_more 40
|
|
67
|
+
|
|
68
|
+
17.times do
|
|
69
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
70
|
+
end
|
|
71
|
+
assert_equal 39, mode.curpos
|
|
72
|
+
assert_equal 0, mode.topline
|
|
73
|
+
|
|
74
|
+
## From the bottom line, it wraps back to the top of the next page.
|
|
75
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
76
|
+
assert_equal 40, mode.curpos
|
|
77
|
+
assert_equal 40, mode.topline
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_scroll_down
|
|
81
|
+
mode = make_mode
|
|
82
|
+
expect_load_more 41
|
|
83
|
+
|
|
84
|
+
## When the cursor is already at the top, it moves with the scroll.
|
|
85
|
+
assert_equal 0, mode.curpos
|
|
86
|
+
assert_equal 0, mode.topline
|
|
87
|
+
mode.handle_input Ncurses::CharCode.character('J')
|
|
88
|
+
assert_equal 1, mode.curpos
|
|
89
|
+
assert_equal 1, mode.topline
|
|
90
|
+
|
|
91
|
+
3.times do
|
|
92
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
93
|
+
end
|
|
94
|
+
assert_equal 4, mode.curpos
|
|
95
|
+
assert_equal 1, mode.topline
|
|
96
|
+
|
|
97
|
+
## When the cursor is not at the top, it keeps its place and the
|
|
98
|
+
## buffer scrolls underneath it.
|
|
99
|
+
mode.handle_input Ncurses::CharCode.character('J')
|
|
100
|
+
assert_equal 4, mode.curpos
|
|
101
|
+
assert_equal 2, mode.topline
|
|
102
|
+
|
|
103
|
+
## It always loads 10 more if we would scroll past the bottom.
|
|
104
|
+
expect_load_more 10
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_page_down
|
|
108
|
+
mode = make_mode
|
|
109
|
+
expect_load_more 41
|
|
110
|
+
|
|
111
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_NPAGE)
|
|
112
|
+
assert_equal 40, mode.curpos
|
|
113
|
+
assert_equal 40, mode.topline
|
|
114
|
+
expect_load_more 40
|
|
115
|
+
|
|
116
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_NPAGE)
|
|
117
|
+
assert_equal 80, mode.curpos
|
|
118
|
+
assert_equal 80, mode.topline
|
|
119
|
+
assert_equal 81, mode.lines
|
|
120
|
+
expect_load_more 40
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_page_down_when_fully_populated
|
|
124
|
+
mode = make_mode
|
|
125
|
+
expect_load_more 41
|
|
126
|
+
(0...119).map { |i| @lines << "more line #{i}" } # enough for 4 full pages
|
|
127
|
+
|
|
128
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_NPAGE)
|
|
129
|
+
assert_equal 40, mode.curpos
|
|
130
|
+
assert_equal 40, mode.topline
|
|
131
|
+
|
|
132
|
+
## Relative cursor position is preserved when paging down.
|
|
133
|
+
3.times do
|
|
134
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
135
|
+
end
|
|
136
|
+
assert_equal 43, mode.curpos
|
|
137
|
+
assert_equal 40, mode.topline
|
|
138
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_NPAGE)
|
|
139
|
+
assert_equal 83, mode.curpos
|
|
140
|
+
assert_equal 80, mode.topline
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_half_page_down
|
|
144
|
+
mode = make_mode
|
|
145
|
+
expect_load_more 41
|
|
146
|
+
|
|
147
|
+
mode.handle_input Ncurses::CharCode.character("\C-d")
|
|
148
|
+
assert_equal 20, mode.curpos
|
|
149
|
+
assert_equal 20, mode.topline
|
|
150
|
+
expect_load_more 40
|
|
151
|
+
|
|
152
|
+
mode.handle_input Ncurses::CharCode.character("\C-d")
|
|
153
|
+
assert_equal 40, mode.curpos
|
|
154
|
+
assert_equal 40, mode.topline
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_half_page_down_when_fully_populated
|
|
158
|
+
mode = make_mode
|
|
159
|
+
expect_load_more 41
|
|
160
|
+
(0...119).map { |i| @lines << "more line #{i}" } # enough for 4 full pages
|
|
161
|
+
|
|
162
|
+
mode.handle_input Ncurses::CharCode.character("\C-d")
|
|
163
|
+
assert_equal 20, mode.curpos
|
|
164
|
+
assert_equal 20, mode.topline
|
|
165
|
+
|
|
166
|
+
25.times do
|
|
167
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
168
|
+
end
|
|
169
|
+
assert_equal 45, mode.curpos
|
|
170
|
+
assert_equal 20, mode.topline
|
|
171
|
+
mode.handle_input Ncurses::CharCode.character("\C-d")
|
|
172
|
+
assert_equal 45, mode.curpos
|
|
173
|
+
assert_equal 40, mode.topline
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_half_page_up
|
|
177
|
+
mode = make_mode
|
|
178
|
+
expect_load_more 41
|
|
179
|
+
(0...119).map { |i| @lines << "more line #{i}" } # enough for 4 full pages
|
|
180
|
+
|
|
181
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_NPAGE)
|
|
182
|
+
assert_equal 40, mode.curpos
|
|
183
|
+
assert_equal 40, mode.topline
|
|
184
|
+
|
|
185
|
+
mode.handle_input Ncurses::CharCode.character("\C-u")
|
|
186
|
+
assert_equal 40, mode.curpos
|
|
187
|
+
assert_equal 20, mode.topline
|
|
188
|
+
|
|
189
|
+
mode.handle_input Ncurses::CharCode.character('j')
|
|
190
|
+
assert_equal 41, mode.curpos
|
|
191
|
+
assert_equal 20, mode.topline
|
|
192
|
+
|
|
193
|
+
mode.handle_input Ncurses::CharCode.character("\C-u")
|
|
194
|
+
assert_equal 40, mode.curpos
|
|
195
|
+
assert_equal 0, mode.topline
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_jump_to_end
|
|
199
|
+
mode = make_mode
|
|
200
|
+
expect_load_more 41
|
|
201
|
+
|
|
202
|
+
mode.handle_input Ncurses::CharCode.keycode(Ncurses::KEY_END)
|
|
203
|
+
assert_equal 40, mode.curpos
|
|
204
|
+
assert_equal 1, mode.topline
|
|
205
|
+
assert_equal 41, mode.botline
|
|
206
|
+
expect_load_more 40
|
|
207
|
+
end
|
|
208
|
+
end
|
metadata
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sup
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.4'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Morgan
|
|
8
8
|
- Gaute Hope
|
|
9
9
|
- Hamish Downer
|
|
10
10
|
- Matthieu Rakotojaona
|
|
11
|
-
autorequire:
|
|
12
11
|
bindir: bin
|
|
13
12
|
cert_chain: []
|
|
14
|
-
date:
|
|
13
|
+
date: 2026-04-21 00:00:00.000000000 Z
|
|
15
14
|
dependencies:
|
|
16
15
|
- !ruby/object:Gem::Dependency
|
|
17
16
|
name: ncursesw
|
|
@@ -370,10 +369,15 @@ files:
|
|
|
370
369
|
- contrib/nix/ruby3.0-Gemfile.lock
|
|
371
370
|
- contrib/nix/ruby3.0-gemset.nix
|
|
372
371
|
- contrib/nix/ruby3.0-shell.nix
|
|
372
|
+
- contrib/nix/ruby3.1-Gemfile.lock
|
|
373
|
+
- contrib/nix/ruby3.1-gemset.nix
|
|
373
374
|
- contrib/nix/ruby3.1-shell.nix
|
|
375
|
+
- contrib/nix/ruby3.2-Gemfile.lock
|
|
376
|
+
- contrib/nix/ruby3.2-gemset.nix
|
|
374
377
|
- contrib/nix/ruby3.2-shell.nix
|
|
375
378
|
- contrib/nix/ruby3.3-shell.nix
|
|
376
379
|
- contrib/nix/ruby3.4-shell.nix
|
|
380
|
+
- contrib/nix/ruby4.0-shell.nix
|
|
377
381
|
- contrib/nix/test-all-rubies.sh
|
|
378
382
|
- devel/console.sh
|
|
379
383
|
- devel/count-loc.sh
|
|
@@ -461,8 +465,8 @@ files:
|
|
|
461
465
|
- man/sup-sync.1
|
|
462
466
|
- man/sup-tweak-labels.1
|
|
463
467
|
- man/sup.1
|
|
464
|
-
- shell.nix
|
|
465
468
|
- sup.gemspec
|
|
469
|
+
- test/dummy_buffer.rb
|
|
466
470
|
- test/dummy_source.rb
|
|
467
471
|
- test/fixtures/bad-content-transfer-encoding-1.eml
|
|
468
472
|
- test/fixtures/binary-content-transfer-encoding-2.eml
|
|
@@ -496,6 +500,7 @@ files:
|
|
|
496
500
|
- test/gnupg_test_home/regen_keys.sh
|
|
497
501
|
- test/gnupg_test_home/secring.gpg
|
|
498
502
|
- test/gnupg_test_home/sup-test-2@foo.bar.asc
|
|
503
|
+
- test/integration/test_draft.rb
|
|
499
504
|
- test/integration/test_maildir.rb
|
|
500
505
|
- test/integration/test_mbox.rb
|
|
501
506
|
- test/integration/test_sup-add.rb
|
|
@@ -510,13 +515,15 @@ files:
|
|
|
510
515
|
- test/unit/test_contact.rb
|
|
511
516
|
- test/unit/test_edit_message_mode.rb
|
|
512
517
|
- test/unit/test_horizontal_selector.rb
|
|
518
|
+
- test/unit/test_index.rb
|
|
519
|
+
- test/unit/test_line_cursor_mode.rb
|
|
513
520
|
- test/unit/test_locale_fiddler.rb
|
|
514
521
|
- test/unit/test_person.rb
|
|
515
522
|
- test/unit/test_rmail_message.rb
|
|
516
523
|
- test/unit/util/test_query.rb
|
|
517
524
|
- test/unit/util/test_string.rb
|
|
518
525
|
- test/unit/util/test_uri.rb
|
|
519
|
-
homepage: https://
|
|
526
|
+
homepage: https://supmua.dev/
|
|
520
527
|
licenses:
|
|
521
528
|
- GPL-2.0
|
|
522
529
|
metadata: {}
|
|
@@ -543,11 +550,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
543
550
|
- !ruby/object:Gem::Version
|
|
544
551
|
version: '0'
|
|
545
552
|
requirements: []
|
|
546
|
-
rubygems_version: 3.
|
|
547
|
-
signing_key:
|
|
553
|
+
rubygems_version: 3.7.2
|
|
548
554
|
specification_version: 4
|
|
549
555
|
summary: A console-based email client with the best features of GMail, mutt and Emacs
|
|
550
556
|
test_files:
|
|
557
|
+
- test/dummy_buffer.rb
|
|
551
558
|
- test/dummy_source.rb
|
|
552
559
|
- test/fixtures/bad-content-transfer-encoding-1.eml
|
|
553
560
|
- test/fixtures/binary-content-transfer-encoding-2.eml
|
|
@@ -581,6 +588,7 @@ test_files:
|
|
|
581
588
|
- test/gnupg_test_home/regen_keys.sh
|
|
582
589
|
- test/gnupg_test_home/secring.gpg
|
|
583
590
|
- test/gnupg_test_home/sup-test-2@foo.bar.asc
|
|
591
|
+
- test/integration/test_draft.rb
|
|
584
592
|
- test/integration/test_maildir.rb
|
|
585
593
|
- test/integration/test_mbox.rb
|
|
586
594
|
- test/integration/test_sup-add.rb
|
|
@@ -595,6 +603,8 @@ test_files:
|
|
|
595
603
|
- test/unit/test_contact.rb
|
|
596
604
|
- test/unit/test_edit_message_mode.rb
|
|
597
605
|
- test/unit/test_horizontal_selector.rb
|
|
606
|
+
- test/unit/test_index.rb
|
|
607
|
+
- test/unit/test_line_cursor_mode.rb
|
|
598
608
|
- test/unit/test_locale_fiddler.rb
|
|
599
609
|
- test/unit/test_person.rb
|
|
600
610
|
- test/unit/test_rmail_message.rb
|
data/shell.nix
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
contrib/nix/ruby3.3-shell.nix
|