]> git.cworth.org Git - sup/blob - lib/sup/textfield.rb
to, cc, bcc prompting and editing, with tab completion
[sup] / lib / sup / textfield.rb
1 require 'curses'
2
3 module Redwood
4
5 ## a fully-functional text field supporting completions, expansions,
6 ## history--everything!
7 ## 
8 ## writing this fucking sucked. if you thought ncurses was some 1970s
9 ## before-people-knew-how-to-program bullshit, wait till you see
10 ## ncurses forms.
11 ##
12 ## completion comments: completion is done emacs-style, and mostly
13 ## depends on outside support, as we merely signal the existence of a
14 ## new set of completions to show (#new_completions?)  or that the
15 ## current list of completions should be rolled if they're too large
16 ## to fill the screen (#roll_completions?).
17 ##
18 ## in sup, completion support is implemented through BufferManager#ask
19 ## and CompletionMode.
20 class TextField
21   def initialize window, y, x, width
22     @w, @x, @y = window, x, y
23     @width = width
24     @i = nil
25     @history = []
26
27     @completion_block = nil
28     reset_completion_state
29   end
30
31   bool_reader :new_completions, :roll_completions
32   attr_reader :completions
33
34   def value; @value || get_cursed_value end
35
36   def activate question, default=nil, &block
37     @question = question
38     @completion_block = block
39     @field = Ncurses::Form.new_field 1, @width - question.length,
40                                      @y, @x + question.length, 0, 0
41     @form = Ncurses::Form.new_form [@field]
42     Ncurses::Form.post_form @form
43     set_cursed_value default if default
44   end
45
46   def position_cursor
47     @w.attrset Colormap.color_for(:none)
48     @w.mvaddstr @y, 0, @question
49     Ncurses.curs_set 1
50     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
51     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED!!!!
52   end
53
54   def deactivate
55     reset_completion_state
56     @form.unpost_form
57     @form.free_form
58     @field.free_field
59     @field = nil
60     Ncurses.curs_set 0
61   end
62
63   def handle_input c
64     ## short-circuit exit paths
65     case c
66     when Ncurses::KEY_ENTER # submit!
67       @value = get_cursed_value
68       @history.push @value
69       return false
70     when Ncurses::KEY_CANCEL # cancel
71       @value = nil
72       return false
73     when Ncurses::KEY_TAB # completion
74       return true unless @completion_block
75       if @completions.empty?
76         v = get_cursed_value
77         c = @completion_block.call v
78         if c.size > 0
79           @value = c.map { |full, short| full }.shared_prefix(true)
80           set_cursed_value @value
81           position_cursor
82         end
83         if c.size > 1
84           @completions = c
85           @new_completions = true
86           @roll_completions = false
87         end
88       else
89         @new_completions = false
90         @roll_completions = true
91       end
92       return true
93     end
94
95     reset_completion_state
96     @value = nil
97
98     d =
99       case c
100       when Ncurses::KEY_LEFT
101         Ncurses::Form::REQ_PREV_CHAR
102       when Ncurses::KEY_RIGHT
103         Ncurses::Form::REQ_NEXT_CHAR
104       when Ncurses::KEY_BACKSPACE
105         Ncurses::Form::REQ_DEL_PREV
106       when 1 #ctrl-a
107         Ncurses::Form::REQ_BEG_FIELD
108       when 5 #ctrl-e
109         Ncurses::Form::REQ_END_FIELD
110       when 11 # ctrl-k
111         Ncurses::Form::REQ_CLR_EOF
112       when Ncurses::KEY_UP
113         @i ||= @history.size
114         @history[@i] = get_cursed_value
115         @i = (@i - 1) % @history.size
116         @value = @history[@i]
117         set_cursed_value @value
118       when Ncurses::KEY_DOWN
119         @i ||= @history.size
120         @history[@i] = get_cursed_value
121         @i = (@i + 1) % @history.size
122         @value = @history[@i]
123         set_cursed_value @value
124       else
125         c
126       end
127
128     Ncurses::Form.form_driver @form, d
129     true
130   end
131
132 private
133
134   def reset_completion_state
135     @completions = []
136     @new_completions = @roll_completions = @clear_completions = false
137   end
138
139   ## ncurses inanity wrapper
140   ##
141   ## DO NOT READ THIS CODE. YOU WILL GO MAD.
142   def get_cursed_value
143     return nil unless @field
144
145     x = Ncurses.curx
146     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
147     v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
148
149     ## cursor <= end of text
150     if x - @question.length - v.length <= 0
151       v
152     else # trailing spaces
153       v + (" " * (x - @question.length - v.length))
154     end
155   end
156
157   def set_cursed_value v
158     @field.set_field_buffer 0, v
159   end
160 end
161 end