1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2018 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "wreadln.hxx"
21 #include "Completion.hxx"
22 #include "charset.hxx"
23 #include "screen_utils.hxx"
26 #include "util/LocaleString.hxx"
27 #include "util/StringUTF8.hxx"
36 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(_WIN32)
57 /** the ncurses window where this field is displayed */
60 /** the origin coordinates in the window */
63 /** the screen width of the input field */
66 /** is the input masked, i.e. characters displayed as '*'? */
69 /** the byte position of the cursor */
72 /** the byte position displayed at the origin (for horizontal
76 /** the current value */
79 wreadln(WINDOW *_w, bool _masked)
80 :w(_w), masked(_masked) {}
83 /** max items stored in the history list */
84 static const guint wrln_max_history_length = 32;
86 /** converts a byte position to a screen column */
89 byte_to_screen(const char *data, size_t x)
91 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
92 assert(x <= strlen(data));
94 const std::string partial(data, x);
95 return locale_width(partial.c_str());
103 /** finds the first character which doesn't fit on the screen */
106 screen_to_bytes(const char *data, unsigned width)
108 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
109 std::string dup(data);
112 unsigned p_width = locale_width(dup.c_str());
113 if (p_width <= width)
121 return (size_t)width;
125 /** returns the screen column where the cursor is located */
128 cursor_column(const struct wreadln *wr)
130 return byte_to_screen(wr->value.data() + wr->start,
131 wr->cursor - wr->start);
134 /** returns the offset in the string to align it at the right border
138 right_align_bytes(const char *data, size_t right, unsigned width)
140 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
143 assert(right <= strlen(data));
145 const std::string dup(data, right);
147 while (start < right) {
148 if (locale_width(dup.c_str() + start) < width)
151 start += CharSizeMB(data + start, right - start);
158 return right >= width ? right + 1 - width : 0;
162 /* move the cursor one step to the right */
163 static inline void cursor_move_right(struct wreadln *wr)
165 if (wr->cursor == wr->value.length())
168 size_t size = CharSizeMB(wr->value.data() + wr->cursor,
169 wr->value.length() - wr->cursor);
171 if (cursor_column(wr) >= wr->width)
172 wr->start = right_align_bytes(wr->value.c_str(),
173 wr->cursor, wr->width);
176 /* move the cursor one step to the left */
177 static inline void cursor_move_left(struct wreadln *wr)
179 const char *v = wr->value.c_str();
180 const char *new_cursor = PrevCharMB(v, v + wr->cursor);
181 wr->cursor = new_cursor - v;
182 if (wr->cursor < wr->start)
183 wr->start = wr->cursor;
186 /* move the cursor to the end of the line */
187 static inline void cursor_move_to_eol(struct wreadln *wr)
189 wr->cursor = wr->value.length();
190 if (cursor_column(wr) >= wr->width)
191 wr->start = right_align_bytes(wr->value.c_str(),
192 wr->cursor, wr->width);
195 /* draw line buffer and update cursor position */
196 static inline void drawline(const struct wreadln *wr)
198 wmove(wr->w, wr->point.y, wr->point.x);
199 /* clear input area */
200 whline(wr->w, ' ', wr->width);
201 /* print visible part of the line buffer */
203 whline(wr->w, '*', utf8_width(wr->value.c_str() + wr->start));
205 waddnstr(wr->w, wr->value.c_str() + wr->start,
206 screen_to_bytes(wr->value.c_str(), wr->width));
207 /* move the cursor to the correct position */
208 wmove(wr->w, wr->point.y, wr->point.x + cursor_column(wr));
209 /* tell ncurses to redraw the screen */
213 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(_WIN32)
215 multibyte_is_complete(const char *p, size_t length)
217 char *q = g_locale_to_utf8(p, length,
218 nullptr, nullptr, nullptr);
229 wreadln_insert_byte(struct wreadln *wr, gint key)
232 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(_WIN32)
233 char buffer[32] = { (char)key };
234 struct pollfd pfd = {
240 /* wide version: try to complete the multibyte sequence */
242 while (length < sizeof(buffer)) {
243 if (multibyte_is_complete(buffer, length))
244 /* sequence is complete */
247 /* poll for more bytes on stdin, without timeout */
249 if (poll(&pfd, 1, 0) <= 0)
250 /* no more input from keyboard */
253 buffer[length++] = wgetch(wr->w);
256 wr->value.insert(wr->cursor, buffer, length);
259 wr->value.insert(wr->cursor, key);
262 wr->cursor += length;
263 if (cursor_column(wr) >= wr->width)
264 wr->start = right_align_bytes(wr->value.c_str(),
265 wr->cursor, wr->width);
269 wreadln_delete_char(struct wreadln *wr, size_t x)
271 assert(x < wr->value.length());
273 size_t length = CharSizeMB(wr->value.data() + x,
274 wr->value.length() - x);
275 wr->value.erase(x, length);
278 /* libcurses version */
283 const char *initial_value,
286 Completion *completion,
289 struct wreadln wr(w, masked);
290 History::iterator hlist, hcurrent;
298 /* make sure the cursor is visible */
300 /* print prompt string */
305 /* retrieve y and x0 position */
306 getyx(w, wr.point.y, wr.point.x);
307 /* check the x1 value */
308 if (x1 <= (unsigned)wr.point.x || x1 > (unsigned)COLS)
310 wr.width = x1 - wr.point.x;
311 /* clear input area */
312 mvwhline(w, wr.point.y, wr.point.x, ' ', wr.width);
315 /* append the a new line to our history list */
316 history->emplace_back();
317 /* hlist points to the current item in the history list */
318 hcurrent = hlist = std::prev(history->end());
321 if (initial_value == (char *)-1) {
322 /* get previous history entry */
323 if (history && hlist != history->begin()) {
324 /* get previous line */
328 cursor_move_to_eol(&wr);
330 } else if (initial_value) {
331 /* copy the initial value to the line buffer */
332 wr.value = initial_value;
333 cursor_move_to_eol(&wr);
338 while (key != 13 && key != '\n') {
341 /* check if key is a function key */
342 for (size_t i = 0; i < 63; i++)
343 if (key == (int)KEY_F(i)) {
350 case KEY_MOUSE: /* ignore mouse events */
352 case ERR: /* ignore errors */
357 if (completion != nullptr) {
358 completion->Pre(wr.value.c_str());
359 auto r = completion->Complete(wr.value.c_str());
360 if (!r.new_prefix.empty()) {
361 wr.value = std::move(r.new_prefix);
362 cursor_move_to_eol(&wr);
366 completion->Post(wr.value.c_str(), r.range);
380 cursor_move_left(&wr);
384 cursor_move_right(&wr);
393 cursor_move_to_eol(&wr);
396 wr.value.erase(wr.cursor);
399 wr.value.erase(0, wr.cursor);
403 /* Firstly remove trailing spaces. */
404 for (; wr.cursor > 0 && wr.value[wr.cursor - 1] == ' ';)
406 cursor_move_left(&wr);
407 wreadln_delete_char(&wr, wr.cursor);
409 /* Then remove word until next space. */
410 for (; wr.cursor > 0 && wr.value[wr.cursor - 1] != ' ';)
412 cursor_move_left(&wr);
413 wreadln_delete_char(&wr, wr.cursor);
417 case KEY_BCKSPC: /* handle backspace: copy all */
418 case KEY_BACKSPACE: /* chars starting from curpos */
419 if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf */
420 cursor_move_left(&wr);
421 wreadln_delete_char(&wr, wr.cursor);
424 case KEY_DC: /* handle delete key. As above */
426 if (wr.cursor < wr.value.length())
427 wreadln_delete_char(&wr, wr.cursor);
431 /* get previous history entry */
432 if (history && hlist != history->begin()) {
433 if (hlist == hcurrent)
434 /* save the current line */
437 /* get previous line */
441 cursor_move_to_eol(&wr);
445 /* get next history entry */
446 if (history && std::next(hlist) != history->end()) {
451 cursor_move_to_eol(&wr);
464 wreadln_insert_byte(&wr, key);
472 if (!wr.value.empty()) {
473 /* update the current history entry */
474 *hcurrent = wr.value;
476 /* the line was empty - remove the current history entry */
477 history->erase(hcurrent);
480 auto history_length = history->size();
481 while (history_length > wrln_max_history_length) {
482 history->pop_front();
487 return std::move(wr.value);
493 const char *initial_value,
496 Completion *completion)
498 return _wreadln(w, prompt, initial_value, x1,
499 history, completion, false);
503 wreadln_masked(WINDOW *w,
505 const char *initial_value,
508 return _wreadln(w, prompt, initial_value, x1, nullptr, nullptr, true);