sup 0.0.8 → 0.1
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.
- data/HACKING +6 -36
- data/History.txt +11 -0
- data/Manifest.txt +5 -0
- data/README.txt +13 -31
- data/Rakefile +3 -3
- data/bin/sup +167 -89
- data/bin/sup-add +39 -29
- data/bin/sup-config +57 -31
- data/bin/sup-sync +60 -54
- data/bin/sup-sync-back +143 -0
- data/doc/FAQ.txt +56 -19
- data/doc/Philosophy.txt +34 -33
- data/doc/TODO +76 -46
- data/doc/UserGuide.txt +142 -122
- data/lib/sup.rb +76 -36
- data/lib/sup/account.rb +27 -19
- data/lib/sup/buffer.rb +130 -44
- data/lib/sup/contact.rb +1 -1
- data/lib/sup/draft.rb +1 -2
- data/lib/sup/imap.rb +64 -19
- data/lib/sup/index.rb +95 -16
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/label.rb +31 -5
- data/lib/sup/maildir.rb +7 -5
- data/lib/sup/mbox.rb +34 -15
- data/lib/sup/mbox/loader.rb +30 -12
- data/lib/sup/mbox/ssh-loader.rb +7 -5
- data/lib/sup/message.rb +93 -44
- data/lib/sup/modes/buffer-list-mode.rb +1 -1
- data/lib/sup/modes/completion-mode.rb +55 -0
- data/lib/sup/modes/compose-mode.rb +6 -25
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +119 -29
- data/lib/sup/modes/file-browser-mode.rb +108 -0
- data/lib/sup/modes/forward-mode.rb +3 -20
- data/lib/sup/modes/inbox-mode.rb +9 -12
- data/lib/sup/modes/label-list-mode.rb +28 -46
- data/lib/sup/modes/label-search-results-mode.rb +1 -16
- data/lib/sup/modes/line-cursor-mode.rb +44 -5
- data/lib/sup/modes/person-search-results-mode.rb +1 -16
- data/lib/sup/modes/reply-mode.rb +18 -31
- data/lib/sup/modes/resume-mode.rb +6 -6
- data/lib/sup/modes/scroll-mode.rb +6 -5
- data/lib/sup/modes/search-results-mode.rb +6 -17
- data/lib/sup/modes/thread-index-mode.rb +70 -28
- data/lib/sup/modes/thread-view-mode.rb +65 -29
- data/lib/sup/person.rb +71 -30
- data/lib/sup/poll.rb +13 -4
- data/lib/sup/rfc2047.rb +61 -0
- data/lib/sup/sent.rb +7 -5
- data/lib/sup/source.rb +12 -9
- data/lib/sup/suicide.rb +36 -0
- data/lib/sup/tagger.rb +6 -6
- data/lib/sup/textfield.rb +76 -14
- data/lib/sup/thread.rb +97 -123
- data/lib/sup/util.rb +167 -1
- metadata +30 -5
data/lib/sup/person.rb
CHANGED
@@ -5,42 +5,89 @@ class PersonManager
|
|
5
5
|
|
6
6
|
def initialize fn
|
7
7
|
@fn = fn
|
8
|
-
|
9
|
-
|
8
|
+
@@people = {}
|
9
|
+
|
10
|
+
## read in stored people
|
11
|
+
IO.readlines(fn).map do |l|
|
12
|
+
l =~ /^(.*)?:\s+(\d+)\s+(.*)$/ or raise "can't parse: #{l}"
|
13
|
+
email, time, name = $1, $2, $3
|
14
|
+
@@people[email] = Person.new name, email, time, false
|
15
|
+
end if File.exists? fn
|
16
|
+
|
10
17
|
self.class.i_am_the_instance self
|
11
18
|
end
|
12
19
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
20
|
+
def save
|
21
|
+
File.open(@fn, "w") do |f|
|
22
|
+
@@people.each do |email, p|
|
23
|
+
f.puts "#{p.email}: #{p.timestamp} #{p.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
16
27
|
|
17
|
-
|
28
|
+
def self.people_for s, opts={}
|
29
|
+
return [] if s.nil?
|
30
|
+
s.split_on_commas.map { |ss| self.person_for ss, opts }
|
31
|
+
end
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@names[email][0] = Time.now.to_i
|
33
|
+
def self.person_for s, opts={}
|
34
|
+
p = Person.from_address(s) or return nil
|
35
|
+
p.definitive = true if opts[:definitive]
|
36
|
+
register p
|
24
37
|
end
|
38
|
+
|
39
|
+
def self.register p
|
40
|
+
oldp = @@people[p.email]
|
25
41
|
|
26
|
-
|
27
|
-
|
42
|
+
if oldp.nil? || p.better_than?(oldp)
|
43
|
+
@@people[p.email] = p
|
44
|
+
end
|
28
45
|
|
29
|
-
|
30
|
-
|
46
|
+
@@people[p.email].touch!
|
47
|
+
@@people[p.email]
|
48
|
+
end
|
49
|
+
end
|
31
50
|
|
32
|
-
|
51
|
+
## don't create these by hand. rather, go through personmanager, to
|
52
|
+
## ensure uniqueness and overriding.
|
53
|
+
class Person
|
54
|
+
attr_accessor :name, :email, :timestamp
|
55
|
+
bool_accessor :definitive
|
33
56
|
|
34
|
-
def initialize name, email
|
57
|
+
def initialize name, email, timestamp=0, definitive=false
|
35
58
|
raise ArgumentError, "email can't be nil" unless email
|
59
|
+
|
60
|
+
if name
|
61
|
+
@name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
|
62
|
+
if @name =~ /^(['"]\s*)(.*?)(\s*["'])$/
|
63
|
+
@name = $2
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
36
67
|
@email = email.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase
|
37
|
-
|
38
|
-
@
|
68
|
+
@definitive = definitive
|
69
|
+
@timestamp = timestamp
|
70
|
+
end
|
71
|
+
|
72
|
+
## heuristic: whether the name attached to this email is "real", i.e.
|
73
|
+
## we should bother to store it.
|
74
|
+
def generic?
|
75
|
+
@email =~ /no\-?reply/
|
76
|
+
end
|
77
|
+
|
78
|
+
def better_than? o
|
79
|
+
return false if o.definitive? || generic?
|
80
|
+
return true if definitive?
|
81
|
+
o.name.nil? || (name && name.length > o.name.length && name =~ /[a-z]/)
|
39
82
|
end
|
40
83
|
|
41
|
-
def
|
42
|
-
|
43
|
-
def
|
84
|
+
def to_s; "#@name <#@email>" end
|
85
|
+
|
86
|
+
def touch!; @timestamp = Time.now.to_i end
|
87
|
+
|
88
|
+
# def == o; o && o.email == email; end
|
89
|
+
# alias :eql? :==
|
90
|
+
# def hash; [name, email].hash; end
|
44
91
|
|
45
92
|
def shortname
|
46
93
|
case @name
|
@@ -93,7 +140,7 @@ class Person
|
|
93
140
|
end.downcase
|
94
141
|
end
|
95
142
|
|
96
|
-
def self.
|
143
|
+
def self.from_address s
|
97
144
|
return nil if s.nil?
|
98
145
|
|
99
146
|
## try and parse an email address and name
|
@@ -110,13 +157,7 @@ class Person
|
|
110
157
|
[nil, s]
|
111
158
|
end
|
112
159
|
|
113
|
-
|
114
|
-
end
|
115
|
-
|
116
|
-
def self.for_several s
|
117
|
-
return [] if s.nil?
|
118
|
-
|
119
|
-
s.split_on_commas.map { |ss| self.for ss }
|
160
|
+
Person.new name, email
|
120
161
|
end
|
121
162
|
end
|
122
163
|
|
data/lib/sup/poll.rb
CHANGED
@@ -9,6 +9,7 @@ class PollManager
|
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@mutex = Mutex.new
|
12
|
+
@thread = nil
|
12
13
|
@last_poll = nil
|
13
14
|
|
14
15
|
self.class.i_am_the_instance self
|
@@ -29,8 +30,8 @@ class PollManager
|
|
29
30
|
[num, numi]
|
30
31
|
end
|
31
32
|
|
32
|
-
def
|
33
|
-
Redwood::reporting_thread do
|
33
|
+
def start
|
34
|
+
@thread = Redwood::reporting_thread do
|
34
35
|
while true
|
35
36
|
sleep DELAY / 2
|
36
37
|
poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
|
@@ -38,6 +39,11 @@ class PollManager
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
42
|
+
def stop
|
43
|
+
@thread.kill if @thread
|
44
|
+
@thread = nil
|
45
|
+
end
|
46
|
+
|
41
47
|
def do_poll
|
42
48
|
total_num = total_numi = 0
|
43
49
|
@mutex.synchronize do
|
@@ -94,18 +100,21 @@ class PollManager
|
|
94
100
|
|
95
101
|
source.each do |offset, labels|
|
96
102
|
if source.has_errors?
|
97
|
-
Redwood::log "error loading messages from #{source}: #{source.
|
103
|
+
Redwood::log "error loading messages from #{source}: #{source.error.message}"
|
98
104
|
return
|
99
105
|
end
|
100
106
|
|
101
107
|
labels.each { |l| LabelManager << l }
|
102
|
-
labels
|
108
|
+
labels = labels + (source.archived? ? [] : [:inbox])
|
103
109
|
|
104
110
|
begin
|
105
111
|
m = Message.new :source => source, :source_info => offset, :labels => labels
|
106
112
|
if m.source_marked_read?
|
107
113
|
m.remove_label :unread
|
108
114
|
labels.delete :unread
|
115
|
+
else
|
116
|
+
m.add_label :unread
|
117
|
+
labels << :unread
|
109
118
|
end
|
110
119
|
|
111
120
|
docid, entry = Index.load_entry_for_id m.id
|
data/lib/sup/rfc2047.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
## from: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/101949
|
2
|
+
|
3
|
+
# $Id: rfc2047.rb,v 1.4 2003/04/18 20:55:56 sam Exp $
|
4
|
+
# MODIFIED slightly by William Morgan
|
5
|
+
#
|
6
|
+
# An implementation of RFC 2047 decoding.
|
7
|
+
#
|
8
|
+
# This module depends on the iconv library by Nobuyoshi Nakada, which I've
|
9
|
+
# heard may be distributed as a standard part of Ruby 1.8. Many thanks to him
|
10
|
+
# for helping with building and using iconv.
|
11
|
+
#
|
12
|
+
# Thanks to "Josef 'Jupp' Schugt" <jupp / gmx.de> for pointing out an error with
|
13
|
+
# stateful character sets.
|
14
|
+
#
|
15
|
+
# Copyright (c) Sam Roberts <sroberts / uniserve.com> 2004
|
16
|
+
#
|
17
|
+
# This file is distributed under the same terms as Ruby.
|
18
|
+
|
19
|
+
require 'iconv'
|
20
|
+
|
21
|
+
module Rfc2047
|
22
|
+
WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
|
23
|
+
WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
|
24
|
+
|
25
|
+
def Rfc2047.is_encoded? s; s =~ WORD end
|
26
|
+
|
27
|
+
# Decodes a string, +from+, containing RFC 2047 encoded words into a target
|
28
|
+
# character set, +target+. See iconv_open(3) for information on the
|
29
|
+
# supported target encodings. If one of the encoded words cannot be
|
30
|
+
# converted to the target encoding, it is left in its encoded form.
|
31
|
+
def Rfc2047.decode_to(target, from)
|
32
|
+
from = from.gsub(WORDSEQ, '\1')
|
33
|
+
out = from.gsub(WORD) do
|
34
|
+
|word|
|
35
|
+
charset, encoding, text = $1, $2, $3
|
36
|
+
|
37
|
+
# B64 or QP decode, as necessary:
|
38
|
+
case encoding
|
39
|
+
when 'b', 'B'
|
40
|
+
#puts text
|
41
|
+
text = text.unpack('m*')[0]
|
42
|
+
#puts text.dump
|
43
|
+
|
44
|
+
when 'q', 'Q'
|
45
|
+
# RFC 2047 has a variant of quoted printable where a ' ' character
|
46
|
+
# can be represented as an '_', rather than =32, so convert
|
47
|
+
# any of these that we find before doing the QP decoding.
|
48
|
+
text = text.tr("_", " ")
|
49
|
+
text = text.unpack('M*')[0]
|
50
|
+
|
51
|
+
# Don't need an else, because no other values can be matched in a
|
52
|
+
# WORD.
|
53
|
+
end
|
54
|
+
|
55
|
+
# Convert:
|
56
|
+
#
|
57
|
+
# Remember - Iconv.open(to, from)!
|
58
|
+
text = Iconv.iconv(target, charset, text).join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/sup/sent.rb
CHANGED
@@ -30,18 +30,20 @@ class SentManager
|
|
30
30
|
end
|
31
31
|
|
32
32
|
class SentLoader < MBox::Loader
|
33
|
+
yaml_properties :cur_offset
|
34
|
+
|
33
35
|
def initialize cur_offset=0
|
34
|
-
filename = Redwood::SENT_FN
|
35
|
-
File.open(filename, "w") { } unless File.exists? filename
|
36
|
-
super "mbox://" + filename, cur_offset, true, true
|
36
|
+
@filename = Redwood::SENT_FN
|
37
|
+
File.open(@filename, "w") { } unless File.exists? @filename
|
38
|
+
super "mbox://" + @filename, cur_offset, true, true
|
37
39
|
end
|
38
40
|
|
41
|
+
def file_path; @filename end
|
42
|
+
|
39
43
|
def uri; SentManager.source_name; end
|
40
44
|
def to_s; SentManager.source_name; end
|
41
45
|
def id; SentManager.source_id; end
|
42
46
|
def labels; [:sent, :inbox]; end
|
43
47
|
end
|
44
48
|
|
45
|
-
Redwood::register_yaml(SentLoader, %w(cur_offset))
|
46
|
-
|
47
49
|
end
|
data/lib/sup/source.rb
CHANGED
@@ -5,15 +5,15 @@ class OutOfSyncSourceError < SourceError; end
|
|
5
5
|
class FatalSourceError < SourceError; end
|
6
6
|
|
7
7
|
class Source
|
8
|
-
## Implementing a new source
|
9
|
-
##
|
8
|
+
## Implementing a new source should be easy, because Sup only needs
|
9
|
+
## to be able to:
|
10
10
|
## 1. See how many messages it contains
|
11
|
-
## 2. Get an
|
11
|
+
## 2. Get an arbitrary message
|
12
12
|
## 3. (optional) see whether the source has marked it read or not
|
13
13
|
##
|
14
14
|
## In particular, Sup doesn't need to move messages, mark them as
|
15
|
-
## read, delete them, or anything else. (Well,
|
16
|
-
##
|
15
|
+
## read, delete them, or anything else. (Well, it's nice to be able
|
16
|
+
## to delete them, but that is optional.)
|
17
17
|
##
|
18
18
|
## On the other hand, Sup assumes that you can assign each message a
|
19
19
|
## unique integer id, such that newer messages have higher ids than
|
@@ -33,7 +33,8 @@ class Source
|
|
33
33
|
## - raw_header offset
|
34
34
|
## - raw_full_message offset
|
35
35
|
## - check
|
36
|
-
## - next (or each, if you prefer)
|
36
|
+
## - next (or each, if you prefer): should return a message and an
|
37
|
+
## array of labels.
|
37
38
|
##
|
38
39
|
## ... where "offset" really means unique id. (You can tell I
|
39
40
|
## started with mbox.)
|
@@ -46,7 +47,7 @@ class Source
|
|
46
47
|
## else (e.g. the imap server is down or the maildir is missing.)
|
47
48
|
##
|
48
49
|
## Finally, be sure the source is thread-safe, since it WILL be
|
49
|
-
##
|
50
|
+
## pummelled from multiple threads at once.
|
50
51
|
##
|
51
52
|
## Examples for you to look at: mbox/loader.rb, imap.rb, and
|
52
53
|
## maildir.rb.
|
@@ -60,6 +61,8 @@ class Source
|
|
60
61
|
attr_accessor :id
|
61
62
|
|
62
63
|
def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil
|
64
|
+
raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id
|
65
|
+
|
63
66
|
@uri = uri
|
64
67
|
@cur_offset = initial_offset
|
65
68
|
@usual = usual
|
@@ -68,6 +71,8 @@ class Source
|
|
68
71
|
@dirty = false
|
69
72
|
end
|
70
73
|
|
74
|
+
def file_path; nil end
|
75
|
+
|
71
76
|
def to_s; @uri.to_s; end
|
72
77
|
def seek_to! o; self.cur_offset = o; end
|
73
78
|
def reset!; seek_to! start_offset; end
|
@@ -97,6 +102,4 @@ protected
|
|
97
102
|
end
|
98
103
|
end
|
99
104
|
|
100
|
-
Redwood::register_yaml(Source, %w(uri cur_offset usual archived id))
|
101
|
-
|
102
105
|
end
|
data/lib/sup/suicide.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class SuicideManager
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
DELAY = 5
|
7
|
+
|
8
|
+
def initialize fn
|
9
|
+
@fn = fn
|
10
|
+
@die = false
|
11
|
+
@thread = nil
|
12
|
+
self.class.i_am_the_instance self
|
13
|
+
FileUtils.rm_f @fn
|
14
|
+
end
|
15
|
+
|
16
|
+
bool_reader :die
|
17
|
+
|
18
|
+
def start
|
19
|
+
@thread = Redwood::reporting_thread do
|
20
|
+
while true
|
21
|
+
sleep DELAY
|
22
|
+
if File.exists? @fn
|
23
|
+
FileUtils.rm_f @fn
|
24
|
+
@die = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@thread.kill if @thread
|
32
|
+
@thread = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/sup/tagger.rb
CHANGED
@@ -12,23 +12,23 @@ class Tagger
|
|
12
12
|
def drop_tag_for o; @tagged.delete o; end
|
13
13
|
|
14
14
|
def apply_to_tagged
|
15
|
-
|
15
|
+
targets = @tagged.select_by_value
|
16
|
+
num_tagged = targets.size
|
16
17
|
if num_tagged == 0
|
17
|
-
BufferManager.flash "No tagged
|
18
|
+
BufferManager.flash "No tagged threads!"
|
18
19
|
return
|
19
20
|
end
|
20
21
|
|
21
|
-
noun = num_tagged == 1 ? "
|
22
|
+
noun = num_tagged == 1 ? "thread" : "threads"
|
22
23
|
c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
|
23
24
|
return if c.nil? # user cancelled
|
24
25
|
|
25
|
-
if(action = @mode.resolve_input
|
26
|
+
if(action = @mode.resolve_input(c))
|
26
27
|
tagged_sym = "multi_#{action}".intern
|
27
28
|
if @mode.respond_to? tagged_sym
|
28
|
-
targets = @tagged.select_by_value
|
29
29
|
@mode.send tagged_sym, targets
|
30
30
|
else
|
31
|
-
BufferManager.flash "That command cannot be applied to multiple
|
31
|
+
BufferManager.flash "That command cannot be applied to multiple threads."
|
32
32
|
end
|
33
33
|
else
|
34
34
|
BufferManager.flash "Unknown command #{c.to_character}."
|
data/lib/sup/textfield.rb
CHANGED
@@ -2,26 +2,47 @@ require 'curses'
|
|
2
2
|
|
3
3
|
module Redwood
|
4
4
|
|
5
|
+
## a fully-functional text field supporting completions, expansions,
|
6
|
+
## history--everything!
|
7
|
+
##
|
8
|
+
## completion is done emacs-style, and mostly depends on outside
|
9
|
+
## support, as we merely signal the existence of a new set of
|
10
|
+
## completions to show (#new_completions?) or that the current list
|
11
|
+
## of completions should be rolled if they're too large to fill the
|
12
|
+
## screen (#roll_completions?).
|
13
|
+
##
|
14
|
+
## in sup, completion support is implemented through BufferManager#ask
|
15
|
+
## and CompletionMode.
|
5
16
|
class TextField
|
6
|
-
attr_reader :value
|
7
|
-
|
8
17
|
def initialize window, y, x, width
|
9
18
|
@w, @x, @y = window, x, y
|
10
19
|
@width = width
|
11
20
|
@i = nil
|
12
21
|
@history = []
|
22
|
+
|
23
|
+
@completion_block = nil
|
24
|
+
reset_completion_state
|
13
25
|
end
|
14
26
|
|
15
|
-
|
27
|
+
bool_reader :new_completions, :roll_completions
|
28
|
+
attr_reader :completions
|
29
|
+
|
30
|
+
## when the user presses enter, we store the value in @value and
|
31
|
+
## clean up all the ncurses cruft. before @value is set, we can
|
32
|
+
## get the current value from ncurses.
|
33
|
+
def value; @field ? get_cur_value : @value end
|
34
|
+
|
35
|
+
def activate question, default=nil, &block
|
16
36
|
@question = question
|
17
37
|
@value = nil
|
38
|
+
@completion_block = block
|
18
39
|
@field = Ncurses::Form.new_field 1, @width - question.length,
|
19
40
|
@y, @x + question.length, 0, 0
|
20
41
|
@form = Ncurses::Form.new_form [@field]
|
21
42
|
|
22
43
|
@history[@i = @history.size] = default || ""
|
23
44
|
Ncurses::Form.post_form @form
|
24
|
-
|
45
|
+
set_cur_value @history[@i]
|
25
46
|
end
|
26
47
|
|
27
48
|
def position_cursor
|
@@ -33,24 +54,47 @@ class TextField
|
|
33
54
|
end
|
34
55
|
|
35
56
|
def deactivate
|
57
|
+
reset_completion_state
|
36
58
|
@form.unpost_form
|
37
59
|
@form.free_form
|
38
60
|
@field.free_field
|
61
|
+
@field = nil
|
39
62
|
Ncurses.curs_set 0
|
40
63
|
end
|
41
64
|
|
42
65
|
def handle_input c
|
43
|
-
|
44
|
-
|
45
|
-
|
66
|
+
## short-circuit exit paths
|
67
|
+
case c
|
68
|
+
when Ncurses::KEY_ENTER # submit!
|
69
|
+
@value = @history[@i] = get_cur_value
|
46
70
|
return false
|
47
|
-
|
71
|
+
when Ncurses::KEY_CANCEL # cancel
|
48
72
|
@history.delete_at @i
|
49
73
|
@i = @history.empty? ? nil : (@i - 1) % @history.size
|
50
74
|
@value = nil
|
51
75
|
return false
|
76
|
+
when Ncurses::KEY_TAB # completion
|
77
|
+
return true unless @completion_block
|
78
|
+
if @completions.empty?
|
79
|
+
v = get_cur_value
|
80
|
+
c = @completion_block.call v
|
81
|
+
if c.size > 0
|
82
|
+
set_cur_value c.map { |full, short| full }.shared_prefix
|
83
|
+
end
|
84
|
+
if c.size > 1
|
85
|
+
@completions = c
|
86
|
+
@new_completions = true
|
87
|
+
@roll_completions = false
|
88
|
+
end
|
89
|
+
else
|
90
|
+
@new_completions = false
|
91
|
+
@roll_completions = true
|
92
|
+
end
|
93
|
+
return true
|
52
94
|
end
|
53
95
|
|
96
|
+
reset_completion_state
|
97
|
+
|
54
98
|
d =
|
55
99
|
case c
|
56
100
|
when Ncurses::KEY_LEFT
|
@@ -64,21 +108,39 @@ class TextField
|
|
64
108
|
when ?\005
|
65
109
|
Ncurses::Form::REQ_END_FIELD
|
66
110
|
when Ncurses::KEY_UP
|
67
|
-
@history[@i] = @field.field_buffer
|
111
|
+
@history[@i] = @field.field_buffer 0
|
68
112
|
@i = (@i - 1) % @history.size
|
69
|
-
|
113
|
+
set_cur_value @history[@i]
|
70
114
|
when Ncurses::KEY_DOWN
|
71
|
-
@history[@i] = @field.field_buffer
|
115
|
+
@history[@i] = @field.field_buffer 0
|
72
116
|
@i = (@i + 1) % @history.size
|
73
|
-
|
117
|
+
set_cur_value @history[@i]
|
74
118
|
else
|
75
119
|
c
|
76
120
|
end
|
77
121
|
|
78
122
|
Ncurses::Form.form_driver @form, d
|
79
|
-
Ncurses.refresh
|
80
|
-
|
81
123
|
true
|
82
124
|
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def reset_completion_state
|
129
|
+
@completions = []
|
130
|
+
@new_completions = @roll_completions = @clear_completions = false
|
131
|
+
end
|
132
|
+
|
133
|
+
## ncurses inanity wrapper
|
134
|
+
def get_cur_value
|
135
|
+
Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
|
136
|
+
@field.field_buffer(0).gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
|
137
|
+
end
|
138
|
+
|
139
|
+
## ncurses inanity wrapper
|
140
|
+
def set_cur_value v
|
141
|
+
@field.set_field_buffer 0, v
|
142
|
+
Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
|
143
|
+
end
|
144
|
+
|
83
145
|
end
|
84
146
|
end
|