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