MPD client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

line-editor.c 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * line-editor.c: a line editor component for the TUI part of liberty
  3. *
  4. * Copyright (c) 2017 - 2018, Přemysl Janouch <p@janouch.name>
  5. *
  6. * Permission to use, copy, modify, and/or distribute this software for any
  7. * purpose with or without fee is hereby granted.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  12. * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  14. * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  15. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. *
  17. */
  18. // This is here just for IDE code model reasons
  19. #ifndef HAVE_LIBERTY
  20. #include "liberty/liberty.c"
  21. #include "liberty/liberty-tui.c"
  22. #endif
  23. static void
  24. row_buffer_append_c (struct row_buffer *self, ucs4_t c, chtype attrs)
  25. {
  26. struct row_char current = { .attrs = attrs, .c = c };
  27. struct row_char invalid = { .attrs = attrs, .c = '?', .width = 1 };
  28. current.width = uc_width (current.c, locale_charset ());
  29. if (current.width < 0 || !app_is_character_in_locale (current.c))
  30. current = invalid;
  31. ARRAY_RESERVE (self->chars, 1);
  32. self->chars[self->chars_len++] = current;
  33. self->total_width += current.width;
  34. }
  35. // --- Line editor -------------------------------------------------------------
  36. enum line_editor_action
  37. {
  38. LINE_EDITOR_B_CHAR, ///< Go back a character
  39. LINE_EDITOR_F_CHAR, ///< Go forward a character
  40. LINE_EDITOR_B_WORD, ///< Go back a word
  41. LINE_EDITOR_F_WORD, ///< Go forward a word
  42. LINE_EDITOR_HOME, ///< Go to start of line
  43. LINE_EDITOR_END, ///< Go to end of line
  44. LINE_EDITOR_B_DELETE, ///< Delete last character
  45. LINE_EDITOR_F_DELETE, ///< Delete next character
  46. LINE_EDITOR_B_KILL_WORD, ///< Delete last word
  47. LINE_EDITOR_B_KILL_LINE, ///< Delete everything up to BOL
  48. LINE_EDITOR_F_KILL_LINE, ///< Delete everything up to EOL
  49. };
  50. struct line_editor
  51. {
  52. int point; ///< Caret index into line data
  53. ucs4_t *line; ///< Line data, 0-terminated
  54. int *w; ///< Codepoint widths, 0-terminated
  55. size_t len; ///< Editor length
  56. size_t alloc; ///< Editor allocated
  57. char prompt; ///< Prompt character
  58. void (*on_changed) (void); ///< Callback on text change
  59. void (*on_end) (bool); ///< Callback on abort
  60. };
  61. static void
  62. line_editor_free (struct line_editor *self)
  63. {
  64. free (self->line);
  65. free (self->w);
  66. }
  67. /// Notify whomever invoked the editor that it's been either confirmed or
  68. /// cancelled and clean up editor state
  69. static void
  70. line_editor_abort (struct line_editor *self, bool status)
  71. {
  72. self->on_end (status);
  73. self->on_changed = NULL;
  74. free (self->line);
  75. self->line = NULL;
  76. free (self->w);
  77. self->w = NULL;
  78. self->alloc = 0;
  79. self->len = 0;
  80. self->point = 0;
  81. self->prompt = 0;
  82. }
  83. /// Start the line editor; remember to fill in "change" and "end" callbacks
  84. static void
  85. line_editor_start (struct line_editor *self, char prompt)
  86. {
  87. self->alloc = 16;
  88. self->line = xcalloc (sizeof *self->line, self->alloc);
  89. self->w = xcalloc (sizeof *self->w, self->alloc);
  90. self->len = 0;
  91. self->point = 0;
  92. self->prompt = prompt;
  93. }
  94. static void
  95. line_editor_changed (struct line_editor *self)
  96. {
  97. self->line[self->len] = 0;
  98. self->w[self->len] = 0;
  99. if (self->on_changed)
  100. self->on_changed ();
  101. }
  102. static void
  103. line_editor_move (struct line_editor *self, int to, int from, int len)
  104. {
  105. memmove (self->line + to, self->line + from,
  106. sizeof *self->line * len);
  107. memmove (self->w + to, self->w + from,
  108. sizeof *self->w * len);
  109. }
  110. static void
  111. line_editor_insert (struct line_editor *self, ucs4_t codepoint)
  112. {
  113. while (self->alloc - self->len < 2 /* inserted + sentinel */)
  114. {
  115. self->alloc <<= 1;
  116. self->line = xreallocarray
  117. (self->line, sizeof *self->line, self->alloc);
  118. self->w = xreallocarray
  119. (self->w, sizeof *self->w, self->alloc);
  120. }
  121. line_editor_move (self, self->point + 1, self->point,
  122. self->len - self->point);
  123. self->line[self->point] = codepoint;
  124. self->w[self->point] = app_is_character_in_locale (codepoint)
  125. ? uc_width (codepoint, locale_charset ())
  126. : 1 /* the replacement question mark */;
  127. self->point++;
  128. self->len++;
  129. line_editor_changed (self);
  130. }
  131. static bool
  132. line_editor_action (struct line_editor *self, enum line_editor_action action)
  133. {
  134. switch (action)
  135. {
  136. default:
  137. return soft_assert (!"unknown line editor action");
  138. case LINE_EDITOR_B_CHAR:
  139. if (self->point < 1)
  140. return false;
  141. do self->point--;
  142. while (self->point > 0
  143. && !self->w[self->point]);
  144. return true;
  145. case LINE_EDITOR_F_CHAR:
  146. if (self->point + 1 > (int) self->len)
  147. return false;
  148. do self->point++;
  149. while (self->point < (int) self->len
  150. && !self->w[self->point]);
  151. return true;
  152. case LINE_EDITOR_B_WORD:
  153. {
  154. if (self->point < 1)
  155. return false;
  156. int i = self->point;
  157. while (i && self->line[--i] == ' ');
  158. while (i-- && self->line[i] != ' ');
  159. self->point = ++i;
  160. return true;
  161. }
  162. case LINE_EDITOR_F_WORD:
  163. {
  164. if (self->point + 1 > (int) self->len)
  165. return false;
  166. int i = self->point;
  167. while (i < (int) self->len && self->line[i] != ' ') i++;
  168. while (i < (int) self->len && self->line[i] == ' ') i++;
  169. self->point = i;
  170. return true;
  171. }
  172. case LINE_EDITOR_HOME:
  173. self->point = 0;
  174. return true;
  175. case LINE_EDITOR_END:
  176. self->point = self->len;
  177. return true;
  178. case LINE_EDITOR_B_DELETE:
  179. {
  180. if (self->point < 1)
  181. return false;
  182. int len = 1;
  183. while (self->point - len > 0
  184. && !self->w[self->point - len])
  185. len++;
  186. line_editor_move (self, self->point - len, self->point,
  187. self->len - self->point);
  188. self->len -= len;
  189. self->point -= len;
  190. line_editor_changed (self);
  191. return true;
  192. }
  193. case LINE_EDITOR_F_DELETE:
  194. {
  195. if (self->point + 1 > (int) self->len)
  196. return false;
  197. int len = 1;
  198. while (self->point + len < (int) self->len
  199. && !self->w[self->point + len])
  200. len++;
  201. self->len -= len;
  202. line_editor_move (self, self->point, self->point + len,
  203. self->len - self->point);
  204. line_editor_changed (self);
  205. return true;
  206. }
  207. case LINE_EDITOR_B_KILL_WORD:
  208. {
  209. if (self->point < 1)
  210. return false;
  211. int i = self->point;
  212. while (i && self->line[--i] == ' ');
  213. while (i-- && self->line[i] != ' ');
  214. i++;
  215. line_editor_move (self, i, self->point, (self->len - self->point));
  216. self->len -= self->point - i;
  217. self->point = i;
  218. line_editor_changed (self);
  219. return true;
  220. }
  221. case LINE_EDITOR_B_KILL_LINE:
  222. self->len -= self->point;
  223. line_editor_move (self, 0, self->point, self->len);
  224. self->point = 0;
  225. line_editor_changed (self);
  226. return true;
  227. case LINE_EDITOR_F_KILL_LINE:
  228. self->len = self->point;
  229. line_editor_changed (self);
  230. return true;
  231. }
  232. }
  233. static int
  234. line_editor_write (const struct line_editor *self, struct row_buffer *row,
  235. int width, chtype attrs)
  236. {
  237. if (self->prompt)
  238. {
  239. hard_assert (self->prompt < 127);
  240. row_buffer_append_c (row, self->prompt, attrs);
  241. width--;
  242. }
  243. int following = 0;
  244. for (size_t i = self->point; i < self->len; i++)
  245. following += self->w[i];
  246. int preceding = 0;
  247. size_t start = self->point;
  248. while (start && preceding < width / 2)
  249. preceding += self->w[--start];
  250. // There can be one extra space at the end of the line but this way we
  251. // don't need to care about non-spacing marks following full-width chars
  252. while (start && width - preceding - following > 2 /* widest char */)
  253. preceding += self->w[--start];
  254. // XXX: we should also show < > indicators for overflow but it'd probably
  255. // considerably complicate this algorithm
  256. for (; start < self->len; start++)
  257. row_buffer_append_c (row, self->line[start], attrs);
  258. return !!self->prompt + preceding;
  259. }