wasplib: add inv management helpers
[waspsaliva.git] / src / terminal_chat_console.cpp
blob9e3d33736190bc6ca2275d43b2ca84f37423c66a
1 /*
2 Minetest
3 Copyright (C) 2015 est31 <MTest31@outlook.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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 "config.h"
21 #if USE_CURSES
22 #include "version.h"
23 #include "terminal_chat_console.h"
24 #include "porting.h"
25 #include "settings.h"
26 #include "util/numeric.h"
27 #include "util/string.h"
28 #include "chat_interface.h"
30 TerminalChatConsole g_term_console;
32 // include this last to avoid any conflicts
33 // (likes to set macros to common names, conflicting various stuff)
34 #if CURSES_HAVE_NCURSESW_NCURSES_H
35 #include <ncursesw/ncurses.h>
36 #elif CURSES_HAVE_NCURSESW_CURSES_H
37 #include <ncursesw/curses.h>
38 #elif CURSES_HAVE_CURSES_H
39 #include <curses.h>
40 #elif CURSES_HAVE_NCURSES_H
41 #include <ncurses.h>
42 #elif CURSES_HAVE_NCURSES_NCURSES_H
43 #include <ncurses/ncurses.h>
44 #elif CURSES_HAVE_NCURSES_CURSES_H
45 #include <ncurses/curses.h>
46 #endif
48 // Some functions to make drawing etc position independent
49 static bool reformat_backend(ChatBackend *backend, int rows, int cols)
51 if (rows < 2)
52 return false;
53 backend->reformat(cols, rows - 2);
54 return true;
57 static void move_for_backend(int row, int col)
59 move(row + 1, col);
62 void TerminalChatConsole::initOfCurses()
64 initscr();
65 cbreak(); //raw();
66 noecho();
67 keypad(stdscr, TRUE);
68 nodelay(stdscr, TRUE);
69 timeout(100);
71 // To make esc not delay up to one second. According to the internet,
72 // this is the value vim uses, too.
73 set_escdelay(25);
75 getmaxyx(stdscr, m_rows, m_cols);
76 m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
79 void TerminalChatConsole::deInitOfCurses()
81 endwin();
84 void *TerminalChatConsole::run()
86 BEGIN_DEBUG_EXCEPTION_HANDLER
88 std::cout << "========================" << std::endl;
89 std::cout << "Begin log output over terminal"
90 << " (no stdout/stderr backlog during that)" << std::endl;
91 // Make the loggers to stdout/stderr shut up.
92 // Go over our own loggers instead.
93 LogLevelMask err_mask = g_logger.removeOutput(&stderr_output);
94 LogLevelMask out_mask = g_logger.removeOutput(&stdout_output);
96 g_logger.addOutput(&m_log_output);
98 // Inform the server of our nick
99 m_chat_interface->command_queue.push_back(
100 new ChatEventNick(CET_NICK_ADD, m_nick));
103 // Ensures that curses is deinitialized even on an exception being thrown
104 CursesInitHelper helper(this);
106 while (!stopRequested()) {
108 int ch = getch();
109 if (stopRequested())
110 break;
112 step(ch);
116 if (m_kill_requested)
117 *m_kill_requested = true;
119 g_logger.removeOutput(&m_log_output);
120 g_logger.addOutputMasked(&stderr_output, err_mask);
121 g_logger.addOutputMasked(&stdout_output, out_mask);
123 std::cout << "End log output over terminal"
124 << " (no stdout/stderr backlog during that)" << std::endl;
125 std::cout << "========================" << std::endl;
127 END_DEBUG_EXCEPTION_HANDLER
129 return NULL;
132 void TerminalChatConsole::typeChatMessage(const std::wstring &msg)
134 // Discard empty line
135 if (msg.empty())
136 return;
138 // Send to server
139 m_chat_interface->command_queue.push_back(
140 new ChatEventChat(m_nick, msg));
142 // Print if its a command (gets eaten by server otherwise)
143 if (msg[0] == L'/') {
144 m_chat_backend.addMessage(L"", (std::wstring)L"Issued command: " + msg);
148 void TerminalChatConsole::handleInput(int ch, bool &complete_redraw_needed)
150 ChatPrompt &prompt = m_chat_backend.getPrompt();
151 // Helpful if you want to collect key codes that aren't documented
152 /*if (ch != ERR) {
153 m_chat_backend.addMessage(L"",
154 (std::wstring)L"Pressed key " + utf8_to_wide(
155 std::string(keyname(ch)) + " (code " + itos(ch) + ")"));
156 complete_redraw_needed = true;
157 }//*/
159 // All the key codes below are compatible to xterm
160 // Only add new ones if you have tried them there,
161 // to ensure compatibility with not just xterm but the wide
162 // range of terminals that are compatible to xterm.
164 switch (ch) {
165 case ERR: // no input
166 break;
167 case 27: // ESC
168 // Toggle ESC mode
169 m_esc_mode = !m_esc_mode;
170 break;
171 case KEY_PPAGE:
172 m_chat_backend.scrollPageUp();
173 complete_redraw_needed = true;
174 break;
175 case KEY_NPAGE:
176 m_chat_backend.scrollPageDown();
177 complete_redraw_needed = true;
178 break;
179 case KEY_ENTER:
180 case '\r':
181 case '\n': {
182 prompt.addToHistory(prompt.getLine());
183 typeChatMessage(prompt.replace(L""));
184 break;
186 case KEY_UP:
187 prompt.historyPrev();
188 break;
189 case KEY_DOWN:
190 prompt.historyNext();
191 break;
192 case KEY_LEFT:
193 // Left pressed
194 // move character to the left
195 prompt.cursorOperation(
196 ChatPrompt::CURSOROP_MOVE,
197 ChatPrompt::CURSOROP_DIR_LEFT,
198 ChatPrompt::CURSOROP_SCOPE_CHARACTER);
199 break;
200 case 545:
201 // Ctrl-Left pressed
202 // move word to the left
203 prompt.cursorOperation(
204 ChatPrompt::CURSOROP_MOVE,
205 ChatPrompt::CURSOROP_DIR_LEFT,
206 ChatPrompt::CURSOROP_SCOPE_WORD);
207 break;
208 case KEY_RIGHT:
209 // Right pressed
210 // move character to the right
211 prompt.cursorOperation(
212 ChatPrompt::CURSOROP_MOVE,
213 ChatPrompt::CURSOROP_DIR_RIGHT,
214 ChatPrompt::CURSOROP_SCOPE_CHARACTER);
215 break;
216 case 560:
217 // Ctrl-Right pressed
218 // move word to the right
219 prompt.cursorOperation(
220 ChatPrompt::CURSOROP_MOVE,
221 ChatPrompt::CURSOROP_DIR_RIGHT,
222 ChatPrompt::CURSOROP_SCOPE_WORD);
223 break;
224 case KEY_HOME:
225 // Home pressed
226 // move to beginning of line
227 prompt.cursorOperation(
228 ChatPrompt::CURSOROP_MOVE,
229 ChatPrompt::CURSOROP_DIR_LEFT,
230 ChatPrompt::CURSOROP_SCOPE_LINE);
231 break;
232 case KEY_END:
233 // End pressed
234 // move to end of line
235 prompt.cursorOperation(
236 ChatPrompt::CURSOROP_MOVE,
237 ChatPrompt::CURSOROP_DIR_RIGHT,
238 ChatPrompt::CURSOROP_SCOPE_LINE);
239 break;
240 case KEY_BACKSPACE:
241 case '\b':
242 case 127:
243 // Backspace pressed
244 // delete character to the left
245 prompt.cursorOperation(
246 ChatPrompt::CURSOROP_DELETE,
247 ChatPrompt::CURSOROP_DIR_LEFT,
248 ChatPrompt::CURSOROP_SCOPE_CHARACTER);
249 break;
250 case KEY_DC:
251 // Delete pressed
252 // delete character to the right
253 prompt.cursorOperation(
254 ChatPrompt::CURSOROP_DELETE,
255 ChatPrompt::CURSOROP_DIR_RIGHT,
256 ChatPrompt::CURSOROP_SCOPE_CHARACTER);
257 break;
258 case 519:
259 // Ctrl-Delete pressed
260 // delete word to the right
261 prompt.cursorOperation(
262 ChatPrompt::CURSOROP_DELETE,
263 ChatPrompt::CURSOROP_DIR_RIGHT,
264 ChatPrompt::CURSOROP_SCOPE_WORD);
265 break;
266 case 21:
267 // Ctrl-U pressed
268 // kill line to left end
269 prompt.cursorOperation(
270 ChatPrompt::CURSOROP_DELETE,
271 ChatPrompt::CURSOROP_DIR_LEFT,
272 ChatPrompt::CURSOROP_SCOPE_LINE);
273 break;
274 case 11:
275 // Ctrl-K pressed
276 // kill line to right end
277 prompt.cursorOperation(
278 ChatPrompt::CURSOROP_DELETE,
279 ChatPrompt::CURSOROP_DIR_RIGHT,
280 ChatPrompt::CURSOROP_SCOPE_LINE);
281 break;
282 case KEY_TAB:
283 // Tab pressed
284 // Nick completion
285 prompt.nickCompletion(m_nicks, false);
286 break;
287 default:
288 // Add character to the prompt,
289 // assuming UTF-8.
290 if (IS_UTF8_MULTB_START(ch)) {
291 m_pending_utf8_bytes.append(1, (char)ch);
292 m_utf8_bytes_to_wait += UTF8_MULTB_START_LEN(ch) - 1;
293 } else if (m_utf8_bytes_to_wait != 0) {
294 m_pending_utf8_bytes.append(1, (char)ch);
295 m_utf8_bytes_to_wait--;
296 if (m_utf8_bytes_to_wait == 0) {
297 std::wstring w = utf8_to_wide(m_pending_utf8_bytes);
298 m_pending_utf8_bytes = "";
299 // hopefully only one char in the wstring...
300 for (size_t i = 0; i < w.size(); i++) {
301 prompt.input(w.c_str()[i]);
304 } else if (IS_ASCII_PRINTABLE_CHAR(ch)) {
305 prompt.input(ch);
306 } else {
307 // Silently ignore characters we don't handle
309 //warningstream << "Pressed invalid character '"
310 // << keyname(ch) << "' (code " << itos(ch) << ")" << std::endl;
312 break;
316 void TerminalChatConsole::step(int ch)
318 bool complete_redraw_needed = false;
320 // empty queues
321 while (!m_chat_interface->outgoing_queue.empty()) {
322 ChatEvent *evt = m_chat_interface->outgoing_queue.pop_frontNoEx();
323 switch (evt->type) {
324 case CET_NICK_REMOVE:
325 m_nicks.remove(((ChatEventNick *)evt)->nick);
326 break;
327 case CET_NICK_ADD:
328 m_nicks.push_back(((ChatEventNick *)evt)->nick);
329 break;
330 case CET_CHAT:
331 complete_redraw_needed = true;
332 // This is only used for direct replies from commands
333 // or for lua's print() functionality
334 m_chat_backend.addMessage(L"", ((ChatEventChat *)evt)->evt_msg);
335 break;
336 case CET_TIME_INFO:
337 ChatEventTimeInfo *tevt = (ChatEventTimeInfo *)evt;
338 m_game_time = tevt->game_time;
339 m_time_of_day = tevt->time;
341 delete evt;
343 while (!m_log_output.queue.empty()) {
344 complete_redraw_needed = true;
345 std::pair<LogLevel, std::string> p = m_log_output.queue.pop_frontNoEx();
346 if (p.first > m_log_level)
347 continue;
349 std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first));
350 if (!g_settings->getBool("disable_escape_sequences")) {
351 error_message = std::wstring(L"\x1b(c@red)").append(error_message)
352 .append(L"\x1b(c@white)");
354 m_chat_backend.addMessage(error_message, utf8_to_wide(p.second));
357 // handle input
358 if (!m_esc_mode) {
359 handleInput(ch, complete_redraw_needed);
360 } else {
361 switch (ch) {
362 case ERR: // no input
363 break;
364 case 27: // ESC
365 // Toggle ESC mode
366 m_esc_mode = !m_esc_mode;
367 break;
368 case 'L':
369 m_log_level--;
370 m_log_level = MYMAX(m_log_level, LL_NONE + 1); // LL_NONE isn't accessible
371 break;
372 case 'l':
373 m_log_level++;
374 m_log_level = MYMIN(m_log_level, LL_MAX - 1);
375 break;
379 // was there a resize?
380 int xn, yn;
381 getmaxyx(stdscr, yn, xn);
382 if (xn != m_cols || yn != m_rows) {
383 m_cols = xn;
384 m_rows = yn;
385 m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
386 complete_redraw_needed = true;
389 // draw title
390 move(0, 0);
391 clrtoeol();
392 addstr(PROJECT_NAME_C);
393 addstr(" ");
394 addstr(g_version_hash);
396 u32 minutes = m_time_of_day % 1000;
397 u32 hours = m_time_of_day / 1000;
398 minutes = (float)minutes / 1000 * 60;
400 if (m_game_time)
401 printw(" | Game %d Time of day %02d:%02d ",
402 m_game_time, hours, minutes);
404 // draw text
405 if (complete_redraw_needed && m_can_draw_text)
406 draw_text();
408 // draw prompt
409 if (!m_esc_mode) {
410 // normal prompt
411 ChatPrompt& prompt = m_chat_backend.getPrompt();
412 std::string prompt_text = wide_to_utf8(prompt.getVisiblePortion());
413 move(m_rows - 1, 0);
414 clrtoeol();
415 addstr(prompt_text.c_str());
416 // Draw cursor
417 s32 cursor_pos = prompt.getVisibleCursorPosition();
418 if (cursor_pos >= 0) {
419 move(m_rows - 1, cursor_pos);
421 } else {
422 // esc prompt
423 move(m_rows - 1, 0);
424 clrtoeol();
425 printw("[ESC] Toggle ESC mode |"
426 " [CTRL+C] Shut down |"
427 " (L) in-, (l) decrease loglevel %s",
428 Logger::getLevelLabel((LogLevel) m_log_level).c_str());
431 refresh();
434 void TerminalChatConsole::draw_text()
436 ChatBuffer& buf = m_chat_backend.getConsoleBuffer();
437 for (u32 row = 0; row < buf.getRows(); row++) {
438 move_for_backend(row, 0);
439 clrtoeol();
440 const ChatFormattedLine& line = buf.getFormattedLine(row);
441 if (line.fragments.empty())
442 continue;
443 for (const ChatFormattedFragment &fragment : line.fragments) {
444 addstr(wide_to_utf8(fragment.text.getString()).c_str());
449 void TerminalChatConsole::stopAndWaitforThread()
451 clearKillStatus();
452 stop();
453 wait();
456 #endif