]> git.cworth.org Git - sup/blob - lib/sup/textfield.rb
tab completion for label editing
[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 ?\001
107         Ncurses::Form::REQ_BEG_FIELD
108       when ?\005
109         Ncurses::Form::REQ_END_FIELD
110       when Ncurses::KEY_UP
111         @i ||= @history.size
112         @history[@i] = get_cursed_value
113         @i = (@i - 1) % @history.size
114         @value = @history[@i]
115         set_cursed_value @value
116       when Ncurses::KEY_DOWN
117         @i ||= @history.size
118         @history[@i] = get_cursed_value
119         @i = (@i + 1) % @history.size
120         @value = @history[@i]
121         set_cursed_value @value
122       else
123         c
124       end
125
126     Ncurses::Form.form_driver @form, d
127     true
128   end
129
130 private
131
132   def reset_completion_state
133     @completions = []
134     @new_completions = @roll_completions = @clear_completions = false
135   end
136
137   ## ncurses inanity wrapper
138   ##
139   ## DO NOT READ THIS CODE. YOU WILL GO MAD.
140   def get_cursed_value
141     return nil unless @field
142
143     x = Ncurses.curx
144     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
145     v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
146
147     ## cursor <= end of text
148     if x - @question.length - v.length <= 0
149       v
150     else # trailing spaces
151       v + (" " * (x - @question.length - v.length))
152     end
153   end
154
155   def set_cursed_value v
156     @field.set_field_buffer 0, v
157   end
158 end
159 end