1 /* Minibuffer facility functions
2 
3    Copyright (c) 1997-2014 Free Software Foundation, Inc.
4 
5    This file is part of GNU Zile.
6 
7    GNU Zile is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3, or (at your option)
10    any later version.
11 
12    GNU Zile is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with GNU Zile; see the file COPYING.  If not, write to the
19    Free Software Foundation, Fifth Floor, 51 Franklin Street, Boston,
20    MA 02111-1301, USA.  */
21 
22 #include <config.h>
23 
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 
29 #include "main.h"
30 #include "extern.h"
31 
32 static History files_history = NULL;
33 static char *minibuf_contents = NULL;
34 
35 /*--------------------------------------------------------------------------
36  * Minibuffer wrapper functions.
37  *--------------------------------------------------------------------------*/
38 
39 void
init_minibuf(void)40 init_minibuf (void)
41 {
42   files_history = history_new ();
43 }
44 
45 bool
minibuf_no_error(void)46 minibuf_no_error (void)
47 {
48   return minibuf_contents == NULL;
49 }
50 
51 void
minibuf_refresh(void)52 minibuf_refresh (void)
53 {
54   if (cur_wp)
55     {
56       if (minibuf_contents)
57         term_minibuf_write (minibuf_contents);
58 
59       /* Redisplay (and leave the cursor in the correct position). */
60       term_redraw_cursor ();
61       term_refresh ();
62     }
63 }
64 
65 _GL_ATTRIBUTE_FORMAT_PRINTF(1, 0) static void
minibuf_vwrite(const char * fmt,va_list ap)66 minibuf_vwrite (const char *fmt, va_list ap)
67 {
68   char *s = xvasprintf (fmt, ap);
69   if (minibuf_contents == NULL || strcmp (s, minibuf_contents))
70     {
71       minibuf_contents = s;
72       minibuf_refresh ();
73     }
74 }
75 
76 /*
77  * Write the specified string in the minibuffer.
78  */
79 void
minibuf_write(const char * fmt,...)80 minibuf_write (const char *fmt, ...)
81 {
82   va_list ap;
83 
84   va_start (ap, fmt);
85   minibuf_vwrite (fmt, ap);
86   va_end (ap);
87 }
88 
89 /*
90  * Write the specified error string in the minibuffer and signal an error.
91  */
92 void
minibuf_error(const char * fmt,...)93 minibuf_error (const char *fmt, ...)
94 {
95   va_list ap;
96 
97   va_start (ap, fmt);
98   minibuf_vwrite (fmt, ap);
99   va_end (ap);
100 
101   ding ();
102 }
103 
104 /*
105  * Read a string from the minibuffer.
106  */
107 const_astr
minibuf_read(const char * fmt,const char * value,...)108 minibuf_read (const char *fmt, const char *value, ...)
109 {
110   va_list ap;
111   char *buf;
112 
113   va_start (ap, value);
114   buf = xvasprintf (fmt, ap);
115   va_end (ap);
116 
117   return term_minibuf_read (buf, value ? value : "", SIZE_MAX, NULL, NULL);
118 }
119 
120 /*
121  * Read a non-negative number from the minibuffer.
122  */
123 long
minibuf_read_number(const char * fmt,...)124 minibuf_read_number (const char *fmt, ...)
125 {
126   va_list ap;
127   char *buf;
128   unsigned long n;
129 
130   va_start (ap, fmt);
131   buf = xvasprintf (fmt, ap);
132   va_end (ap);
133 
134   do
135     {
136       const_astr as = minibuf_read ("%s", "", buf);
137       if (as == NULL || astr_cstr (as) == NULL)
138         {
139           n = LONG_MAX;
140           FUNCALL (keyboard_quit);
141           break;
142         }
143       if (strlen (astr_cstr (as)) == 0)
144         n = LONG_MAX - 1;
145       else
146         n = strtoul (astr_cstr (as), NULL, 10);
147       if (n == LONG_MAX)
148         {
149           minibuf_write ("Please enter a number.");
150           ding ();
151         }
152     }
153   while (n == LONG_MAX);
154 
155   return n;
156 }
157 
158 /*
159  * Read a filename from the minibuffer.
160  */
161 const_astr
minibuf_read_filename(const char * fmt,const char * value,const char * file,...)162 minibuf_read_filename (const char *fmt, const char *value,
163                        const char *file, ...)
164 {
165   const_astr p = NULL;
166 
167   astr as = astr_new_cstr (value);
168   if (file == NULL && astr_len (as) > 0 && astr_get (as, astr_len (as) - 1) != '/')
169     astr_cat_char (as, '/');
170 
171   if (expand_path (as))
172     {
173       va_list ap;
174       va_start (ap, file);
175       char *buf = xvasprintf (fmt, ap);
176       va_end (ap);
177 
178       as = compact_path (as);
179 
180       Completion cp = completion_new (true);
181       size_t pos = astr_len (as);
182       if (file)
183         pos -= strlen (file);
184       p = term_minibuf_read (buf, astr_cstr (as), pos, cp, files_history);
185 
186       if (p != NULL)
187         {
188           astr q = astr_cpy (astr_new (), p);
189           if (expand_path (q))
190             add_history_element (files_history, astr_cstr (q));
191           else
192             q = NULL;
193           p = q;
194         }
195     }
196 
197   return p;
198 }
199 
200 bool
minibuf_test_in_completions(const char * ms,gl_list_t completions)201 minibuf_test_in_completions (const char *ms, gl_list_t completions)
202 {
203   return gl_sortedlist_search (completions, completion_strcmp, ms) != NULL;
204 }
205 
206 int
minibuf_read_yesno(const char * fmt,...)207 minibuf_read_yesno (const char *fmt, ...)
208 {
209   va_list ap;
210   const char *errmsg = "Please answer yes or no.";
211   Completion cp = completion_new (false);
212   int ret = -1;
213 
214   gl_sortedlist_add (get_completion_completions (cp), completion_strcmp, xstrdup ("yes"));
215   gl_sortedlist_add (get_completion_completions (cp), completion_strcmp, xstrdup ("no"));
216 
217   va_start (ap, fmt);
218   const_astr ms = minibuf_vread_completion (fmt, "", cp, NULL, errmsg,
219                                             minibuf_test_in_completions, errmsg, ap);
220   va_end (ap);
221 
222   if (ms != NULL)
223     {
224       gl_list_node_t n = gl_sortedlist_search (get_completion_completions (cp),
225                                                completion_strcmp, astr_cstr (ms));
226       assert (n);
227       ret = STREQ ((const char *) gl_list_node_value (get_completion_completions (cp), n),
228                    "yes");
229     }
230 
231   return ret;
232 }
233 
234 const_astr
minibuf_read_completion(const char * fmt,const char * value,Completion cp,History hp,...)235 minibuf_read_completion (const char *fmt, const char *value, Completion cp, History hp, ...)
236 {
237   va_list ap;
238   char *buf;
239 
240   va_start (ap, hp);
241   buf = xvasprintf (fmt, ap);
242   va_end (ap);
243 
244   return term_minibuf_read (buf, value, SIZE_MAX, cp, hp);
245 }
246 
247 /*
248  * Read a string from the minibuffer using a completion.
249  */
250 const_astr
minibuf_vread_completion(const char * fmt,const char * value,Completion cp,History hp,const char * empty_err,bool (* test)(const char * s,gl_list_t completions),const char * invalid_err,va_list ap)251 minibuf_vread_completion (const char *fmt, const char *value, Completion cp,
252                           History hp, const char *empty_err,
253                           bool (*test) (const char *s, gl_list_t completions),
254                           const char *invalid_err, va_list ap)
255 {
256   const_astr ms;
257   char *buf = xvasprintf (fmt, ap);
258 
259   for (;;)
260     {
261       ms = term_minibuf_read (buf, value, SIZE_MAX, cp, hp);
262 
263       if (ms == NULL) /* Cancelled. */
264         {
265           FUNCALL (keyboard_quit);
266           break;
267         }
268       else if (astr_len (ms) == 0)
269         {
270           minibuf_error ("%s", empty_err);
271           ms = NULL;
272           break;
273         }
274       else
275         {
276           astr as = astr_cpy (astr_new (), ms);
277           /* Complete partial words if possible. */
278           if (completion_try (cp, as, false) == completion_matched)
279             ms = astr_new_cstr (get_completion_match (cp));
280 
281           if (test (astr_cstr (ms), get_completion_completions (cp)))
282             {
283               if (hp)
284                 add_history_element (hp, astr_cstr (ms));
285               minibuf_clear ();
286               break;
287             }
288           else
289             {
290 #pragma GCC diagnostic push
291 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
292               minibuf_error (invalid_err, astr_cstr (ms));
293 #pragma GCC diagnostic pop
294               waitkey ();
295             }
296         }
297     }
298 
299   return ms;
300 }
301 
302 /*
303  * Clear the minibuffer.
304  */
305 void
minibuf_clear(void)306 minibuf_clear (void)
307 {
308   term_minibuf_write ("");
309 }
310