| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> | 
					
						
							|  |  |  |  * All rights reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Redistribution and use in source and binary forms, with or without | 
					
						
							|  |  |  |  * modification, are permitted provided that the following conditions are met: | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 1. Redistributions of source code must retain the above copyright notice, this | 
					
						
							|  |  |  |  *    list of conditions and the following disclaimer. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 2. Redistributions in binary form must reproduce the above copyright notice, | 
					
						
							|  |  |  |  *    this list of conditions and the following disclaimer in the documentation | 
					
						
							|  |  |  |  *    and/or other materials provided with the distribution. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
					
						
							|  |  |  |  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
					
						
							|  |  |  |  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
					
						
							|  |  |  |  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
					
						
							|  |  |  |  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
					
						
							|  |  |  |  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
					
						
							|  |  |  |  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
					
						
							|  |  |  |  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
					
						
							|  |  |  |  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
					
						
							|  |  |  |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:57 +02:00
										 |  |  | #include "Editor.h"
 | 
					
						
							| 
									
										
										
										
											2020-04-01 11:24:10 +02:00
										 |  |  | #include <AK/StringBuilder.h>
 | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | #include <ctype.h>
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							|  |  |  | #include <sys/ioctl.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | namespace Line { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  | Editor::Editor(bool always_refresh) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |     m_always_refresh = always_refresh; | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     m_pending_chars = ByteBuffer::create_uninitialized(0); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |     struct winsize ws; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |     if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |         m_num_columns = 80; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |         m_num_lines = 25; | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |         m_num_columns = ws.ws_col; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |         m_num_lines = ws.ws_row; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | Editor::~Editor() | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-02 01:29:48 +04:30
										 |  |  |     if (m_initialized) | 
					
						
							|  |  |  |         tcsetattr(0, TCSANOW, &m_default_termios); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::add_to_history(const String& line) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     if ((m_history.size() + 1) > m_history_capacity) | 
					
						
							|  |  |  |         m_history.take_first(); | 
					
						
							|  |  |  |     m_history.append(line); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::clear_line() | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     for (size_t i = 0; i < m_cursor; ++i) | 
					
						
							|  |  |  |         fputc(0x8, stdout); | 
					
						
							|  |  |  |     fputs("\033[K", stdout); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  |     m_buffer.clear(); | 
					
						
							|  |  |  |     m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::insert(const String& string) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |     for (auto ch : string) | 
					
						
							|  |  |  |         insert(ch); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::insert(const char ch) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     m_pending_chars.append(&ch, 1); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |     if (m_cursor == m_buffer.size()) { | 
					
						
							|  |  |  |         m_buffer.append(ch); | 
					
						
							|  |  |  |         m_cursor = m_buffer.size(); | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |         m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_buffer.insert(m_cursor, ch); | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     ++m_chars_inserted_in_the_middle; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |     ++m_cursor; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:37:01 +02:00
										 |  |  | void Editor::register_character_input_callback(char ch, Function<bool(Editor&)> callback) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     if (m_key_callbacks.contains(ch)) { | 
					
						
							|  |  |  |         dbg() << "Key callback registered twice for " << ch; | 
					
						
							|  |  |  |         ASSERT_NOT_REACHED(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_key_callbacks.set(ch, make<KeyCallback>(move(callback))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  | void Editor::stylize(const Span& span, const Style& style) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto starting_map = m_spans_starting.get(span.beginning()).value_or({}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!starting_map.contains(span.end())) | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     starting_map.set(span.end(), style); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_spans_starting.set(span.beginning(), starting_map); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto ending_map = m_spans_ending.get(span.end()).value_or({}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!ending_map.contains(span.beginning())) | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							|  |  |  |     ending_map.set(span.beginning(), style); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_spans_ending.set(span.end(), ending_map); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | String Editor::get_line(const String& prompt) | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     set_prompt(prompt); | 
					
						
							|  |  |  |     reset(); | 
					
						
							| 
									
										
										
										
											2020-04-11 17:22:24 +04:30
										 |  |  |     set_origin(); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     m_history_cursor = m_history.size(); | 
					
						
							|  |  |  |     for (;;) { | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |         if (m_always_refresh) | 
					
						
							|  |  |  |             m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |         refresh_display(); | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |         if (m_finish) { | 
					
						
							|  |  |  |             m_finish = false; | 
					
						
							|  |  |  |             printf("\n"); | 
					
						
							|  |  |  |             fflush(stdout); | 
					
						
							|  |  |  |             auto string = String::copy(m_buffer); | 
					
						
							|  |  |  |             m_buffer.clear(); | 
					
						
							|  |  |  |             return string; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |         char keybuf[16]; | 
					
						
							|  |  |  |         ssize_t nread = read(0, keybuf, sizeof(keybuf)); | 
					
						
							|  |  |  |         // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
 | 
					
						
							|  |  |  |         if (nread == 0) | 
					
						
							|  |  |  |             exit(0); | 
					
						
							|  |  |  |         if (nread < 0) { | 
					
						
							|  |  |  |             if (errno == EINTR) { | 
					
						
							| 
									
										
										
										
											2020-04-20 17:53:24 +04:30
										 |  |  |                 if (!m_was_interrupted) { | 
					
						
							|  |  |  |                     if (m_was_resized) | 
					
						
							|  |  |  |                         continue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_was_interrupted = false; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:53:24 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     if (!m_buffer.is_empty()) | 
					
						
							|  |  |  |                         printf("^C"); | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 17:53:24 +04:30
										 |  |  |                     m_buffer.clear(); | 
					
						
							|  |  |  |                     m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                 finish(); | 
					
						
							|  |  |  |                 continue; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |             } | 
					
						
							|  |  |  |             perror("read failed"); | 
					
						
							|  |  |  |             // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead.
 | 
					
						
							|  |  |  |             exit(2); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |         auto reverse_tab = false; | 
					
						
							|  |  |  |         auto increment_suggestion_index = [&] { | 
					
						
							| 
									
										
										
										
											2020-04-15 18:26:11 +04:30
										 |  |  |             if (m_suggestions.size()) | 
					
						
							|  |  |  |                 m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size(); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 m_next_suggestion_index = 0; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |         }; | 
					
						
							|  |  |  |         auto decrement_suggestion_index = [&] { | 
					
						
							|  |  |  |             if (m_next_suggestion_index == 0) | 
					
						
							|  |  |  |                 m_next_suggestion_index = m_suggestions.size(); | 
					
						
							|  |  |  |             m_next_suggestion_index--; | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |         auto ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |         for (ssize_t i = 0; i < nread; ++i) { | 
					
						
							|  |  |  |             char ch = keybuf[i]; | 
					
						
							|  |  |  |             if (ch == 0) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             switch (m_state) { | 
					
						
							|  |  |  |             case InputState::ExpectBracket: | 
					
						
							|  |  |  |                 if (ch == '[') { | 
					
						
							|  |  |  |                     m_state = InputState::ExpectFinal; | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     m_state = InputState::Free; | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             case InputState::ExpectFinal: | 
					
						
							|  |  |  |                 switch (ch) { | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                 case 'O': // mod_ctrl
 | 
					
						
							|  |  |  |                     ctrl_held = true; | 
					
						
							|  |  |  |                     continue; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 case 'A': // up
 | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                 { | 
					
						
							|  |  |  |                     m_searching_backwards = true; | 
					
						
							|  |  |  |                     auto inline_search_cursor = m_inline_search_cursor; | 
					
						
							|  |  |  |                     String search_phrase { m_buffer.data(), inline_search_cursor }; | 
					
						
							|  |  |  |                     if (search(search_phrase, true, true)) { | 
					
						
							|  |  |  |                         ++m_search_offset; | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         insert(search_phrase); | 
					
						
							| 
									
										
										
										
											2020-04-10 09:35:53 +04:30
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = inline_search_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 case 'B': // down
 | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                 { | 
					
						
							|  |  |  |                     auto inline_search_cursor = m_inline_search_cursor; | 
					
						
							|  |  |  |                     String search_phrase { m_buffer.data(), inline_search_cursor }; | 
					
						
							|  |  |  |                     auto search_changed_directions = m_searching_backwards; | 
					
						
							|  |  |  |                     m_searching_backwards = false; | 
					
						
							|  |  |  |                     if (m_search_offset > 0) { | 
					
						
							|  |  |  |                         m_search_offset -= 1 + search_changed_directions; | 
					
						
							|  |  |  |                         if (!search(search_phrase, true, true)) { | 
					
						
							|  |  |  |                             insert(search_phrase); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         m_search_offset = 0; | 
					
						
							|  |  |  |                         m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2020-04-10 09:35:53 +04:30
										 |  |  |                         m_buffer.clear(); | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                         insert(search_phrase); | 
					
						
							| 
									
										
										
										
											2020-04-10 09:35:53 +04:30
										 |  |  |                         m_refresh_needed = true; | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = inline_search_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 case 'D': // left
 | 
					
						
							|  |  |  |                     if (m_cursor > 0) { | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                         if (ctrl_held) { | 
					
						
							|  |  |  |                             auto skipped_at_least_one_character = false; | 
					
						
							|  |  |  |                             for (;;) { | 
					
						
							|  |  |  |                                 if (m_cursor == 0) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                                 if (skipped_at_least_one_character && isspace(m_buffer[m_cursor - 1])) // stop *after* a space, but only if it changes the position
 | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                                 skipped_at_least_one_character = true; | 
					
						
							|  |  |  |                                 --m_cursor; | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             --m_cursor; | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 case 'C': // right
 | 
					
						
							|  |  |  |                     if (m_cursor < m_buffer.size()) { | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                         if (ctrl_held) { | 
					
						
							|  |  |  |                             // temporarily put a space at the end of our buffer
 | 
					
						
							|  |  |  |                             // this greatly simplifies the logic below
 | 
					
						
							|  |  |  |                             m_buffer.append(' '); | 
					
						
							|  |  |  |                             for (;;) { | 
					
						
							|  |  |  |                                 if (m_cursor >= m_buffer.size()) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                                 if (isspace(m_buffer[++m_cursor])) | 
					
						
							|  |  |  |                                     break; | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                             m_buffer.take_last(); | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             ++m_cursor; | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |                     m_search_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 case 'H': | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                     m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |                     m_search_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 case 'F': | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                     m_cursor = m_buffer.size(); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |                     m_search_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |                 case 'Z': // shift+tab
 | 
					
						
							|  |  |  |                     reverse_tab = true; | 
					
						
							|  |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 case '3': | 
					
						
							| 
									
										
										
										
											2020-04-13 14:51:34 +04:30
										 |  |  |                     if (m_cursor == m_buffer.size()) { | 
					
						
							|  |  |  |                         fputc('\a', stdout); | 
					
						
							|  |  |  |                         fflush(stdout); | 
					
						
							|  |  |  |                         continue; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     m_buffer.remove(m_cursor); | 
					
						
							|  |  |  |                     m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                     m_search_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::ExpectTerminator; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 default: | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     dbgprintf("Shell: Unhandled final: %02x (%c)\r\n", ch, ch); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     m_state = InputState::Free; | 
					
						
							| 
									
										
										
										
											2020-04-20 22:05:03 +04:30
										 |  |  |                     ctrl_held = false; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case InputState::ExpectTerminator: | 
					
						
							|  |  |  |                 m_state = InputState::Free; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             case InputState::Free: | 
					
						
							|  |  |  |                 if (ch == 27) { | 
					
						
							|  |  |  |                     m_state = InputState::ExpectBracket; | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auto cb = m_key_callbacks.get(ch); | 
					
						
							|  |  |  |             if (cb.has_value()) { | 
					
						
							|  |  |  |                 if (!cb.value()->callback(*this)) { | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |             m_search_offset = 0; // reset search offset on any key
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |             if (ch == '\t' || reverse_tab) { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 if (!on_tab_complete_first_token || !on_tab_complete_other_token) | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 bool is_empty_token = m_cursor == 0 || m_buffer[m_cursor - 1] == ' '; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 // reverse tab can count as regular tab here
 | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 m_times_tab_pressed++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 int token_start = m_cursor - 1; | 
					
						
							|  |  |  |                 if (!is_empty_token) { | 
					
						
							|  |  |  |                     while (token_start >= 0 && m_buffer[token_start] != ' ') | 
					
						
							|  |  |  |                         --token_start; | 
					
						
							|  |  |  |                     ++token_start; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 bool is_first_token = true; | 
					
						
							|  |  |  |                 for (int i = token_start - 1; i >= 0; --i) { | 
					
						
							|  |  |  |                     if (m_buffer[i] != ' ') { | 
					
						
							|  |  |  |                         is_first_token = false; | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 // ask for completions only on the first tab
 | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                 // and scan for the largest common prefix to display
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 // further tabs simply show the cached completions
 | 
					
						
							|  |  |  |                 if (m_times_tab_pressed == 1) { | 
					
						
							|  |  |  |                     if (is_first_token) | 
					
						
							|  |  |  |                         m_suggestions = on_tab_complete_first_token(token); | 
					
						
							|  |  |  |                     else | 
					
						
							|  |  |  |                         m_suggestions = on_tab_complete_other_token(token); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                     size_t common_suggestion_prefix { 0 }; | 
					
						
							|  |  |  |                     if (m_suggestions.size() == 1) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                         m_largest_common_suggestion_prefix_length = m_suggestions[0].text.length(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                     } else if (m_suggestions.size()) { | 
					
						
							|  |  |  |                         char last_valid_suggestion_char; | 
					
						
							|  |  |  |                         for (;; ++common_suggestion_prefix) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             if (m_suggestions[0].text.length() <= common_suggestion_prefix) | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                                 goto no_more_commons; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             last_valid_suggestion_char = m_suggestions[0].text[common_suggestion_prefix]; | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |                             for (const auto& suggestion : m_suggestions) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                                 if (suggestion.text.length() < common_suggestion_prefix || suggestion.text[common_suggestion_prefix] != last_valid_suggestion_char) { | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                                     goto no_more_commons; | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     no_more_commons:; | 
					
						
							|  |  |  |                         m_largest_common_suggestion_prefix_length = common_suggestion_prefix; | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         m_largest_common_suggestion_prefix_length = 0; | 
					
						
							|  |  |  |                         // there are no suggestions, beep~
 | 
					
						
							|  |  |  |                         putchar('\a'); | 
					
						
							|  |  |  |                         fflush(stdout); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                     m_prompt_lines_at_suggestion_initiation = num_lines(); | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:35:39 +01:00
										 |  |  |                 // Adjust already incremented / decremented index when switching tab direction
 | 
					
						
							|  |  |  |                 if (reverse_tab && m_tab_direction != TabDirection::Backward) { | 
					
						
							|  |  |  |                     decrement_suggestion_index(); | 
					
						
							|  |  |  |                     decrement_suggestion_index(); | 
					
						
							|  |  |  |                     m_tab_direction = TabDirection::Backward; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (!reverse_tab && m_tab_direction != TabDirection::Forward) { | 
					
						
							|  |  |  |                     increment_suggestion_index(); | 
					
						
							|  |  |  |                     increment_suggestion_index(); | 
					
						
							|  |  |  |                     m_tab_direction = TabDirection::Forward; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 reverse_tab = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 auto current_suggestion_index = m_next_suggestion_index; | 
					
						
							|  |  |  |                 if (m_next_suggestion_index < m_suggestions.size()) { | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                     auto can_complete = m_next_suggestion_invariant_offset < m_largest_common_suggestion_prefix_length; | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                     if (!m_last_shown_suggestion.text.is_null()) { | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                         size_t actual_offset; | 
					
						
							|  |  |  |                         size_t shown_length = m_last_shown_suggestion_display_length; | 
					
						
							|  |  |  |                         switch (m_times_tab_pressed) { | 
					
						
							|  |  |  |                         case 1: | 
					
						
							|  |  |  |                             actual_offset = m_cursor; | 
					
						
							|  |  |  |                             break; | 
					
						
							|  |  |  |                         case 2: | 
					
						
							|  |  |  |                             actual_offset = m_cursor - m_largest_common_suggestion_prefix_length + m_next_suggestion_invariant_offset; | 
					
						
							|  |  |  |                             if (can_complete) | 
					
						
							| 
									
										
										
										
											2020-04-19 21:57:14 +04:30
										 |  |  |                                 shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trailing_trivia.length(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                             break; | 
					
						
							|  |  |  |                         default: | 
					
						
							|  |  |  |                             if (m_last_shown_suggestion_display_length == 0) | 
					
						
							|  |  |  |                                 actual_offset = m_cursor; | 
					
						
							|  |  |  |                             else | 
					
						
							|  |  |  |                                 actual_offset = m_cursor - m_last_shown_suggestion_display_length + m_next_suggestion_invariant_offset; | 
					
						
							|  |  |  |                             break; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         for (size_t i = m_next_suggestion_invariant_offset; i < shown_length; ++i) | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                             m_buffer.remove(actual_offset); | 
					
						
							|  |  |  |                         m_cursor = actual_offset; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                         m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                         m_refresh_needed = true; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     m_last_shown_suggestion = m_suggestions[m_next_suggestion_index]; | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                     m_last_shown_suggestion_display_length = m_last_shown_suggestion.text.length(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                     m_last_shown_suggestion_was_complete = true; | 
					
						
							|  |  |  |                     if (m_times_tab_pressed == 1) { | 
					
						
							|  |  |  |                         // This is the first time, so only auto-complete *if possible*
 | 
					
						
							|  |  |  |                         if (can_complete) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset)); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                             m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length; | 
					
						
							|  |  |  |                             // do not increment the suggestion index, as the first tab should only be a *peek*
 | 
					
						
							|  |  |  |                             if (m_suggestions.size() == 1) { | 
					
						
							|  |  |  |                                 // if there's one suggestion, commit and forget
 | 
					
						
							|  |  |  |                                 m_times_tab_pressed = 0; | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                                 // add in the trivia of the last selected suggestion
 | 
					
						
							|  |  |  |                                 insert(m_last_shown_suggestion.trailing_trivia); | 
					
						
							|  |  |  |                                 m_last_shown_suggestion_display_length += m_last_shown_suggestion.trailing_trivia.length(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                             } | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             m_last_shown_suggestion_display_length = 0; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         ++m_times_tab_pressed; | 
					
						
							|  |  |  |                         m_last_shown_suggestion_was_complete = false; | 
					
						
							|  |  |  |                     } else { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                         insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_last_shown_suggestion.text.length() - m_next_suggestion_invariant_offset)); | 
					
						
							|  |  |  |                         // add in the trivia of the last selected suggestion
 | 
					
						
							|  |  |  |                         insert(m_last_shown_suggestion.trailing_trivia); | 
					
						
							| 
									
										
										
										
											2020-04-19 21:57:14 +04:30
										 |  |  |                         m_last_shown_suggestion_display_length += m_last_shown_suggestion.trailing_trivia.length(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                         if (m_tab_direction == TabDirection::Forward) | 
					
						
							|  |  |  |                             increment_suggestion_index(); | 
					
						
							|  |  |  |                         else | 
					
						
							|  |  |  |                             decrement_suggestion_index(); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 } else { | 
					
						
							|  |  |  |                     m_next_suggestion_index = 0; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 if (m_times_tab_pressed > 1 && !m_suggestions.is_empty()) { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     size_t longest_suggestion_length = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                     for (auto& suggestion : m_suggestions) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                         longest_suggestion_length = max(longest_suggestion_length, suggestion.text.length()); | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |                     size_t num_printed = 0; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |                     size_t lines_used { 1 }; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                     size_t index { 0 }; | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                     vt_save_cursor(); | 
					
						
							|  |  |  |                     vt_clear_lines(0, m_lines_used_for_last_suggestions); | 
					
						
							|  |  |  |                     vt_restore_cursor(); | 
					
						
							|  |  |  |                     auto spans_entire_line { false }; | 
					
						
							|  |  |  |                     auto max_line_count = (m_cached_prompt_length + longest_suggestion_length + m_num_columns - 1) / m_num_columns; | 
					
						
							|  |  |  |                     if (longest_suggestion_length >= m_num_columns - 2) { | 
					
						
							|  |  |  |                         spans_entire_line = true; | 
					
						
							|  |  |  |                         // we should make enough space for the biggest entry in
 | 
					
						
							|  |  |  |                         // the suggestion list to fit in the prompt line
 | 
					
						
							|  |  |  |                         auto start = max_line_count - m_prompt_lines_at_suggestion_initiation; | 
					
						
							|  |  |  |                         for (size_t i = start; i < max_line_count; ++i) { | 
					
						
							|  |  |  |                             putchar('\n'); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         lines_used += max_line_count; | 
					
						
							|  |  |  |                         longest_suggestion_length = 0; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     vt_move_absolute(max_line_count + m_origin_x, 1); | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                     for (auto& suggestion : m_suggestions) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                         size_t next_column = num_printed + suggestion.text.length() + longest_suggestion_length + 2; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |                         if (next_column > m_num_columns) { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             auto lines = (suggestion.text.length() + m_num_columns - 1) / m_num_columns; | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                             lines_used += lines; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                             putchar('\n'); | 
					
						
							|  |  |  |                             num_printed = 0; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                         // show just enough suggestions to fill up the screen
 | 
					
						
							|  |  |  |                         // without moving the prompt out of view
 | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                         if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                             break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                         // only apply colour to the selection if something is *actually* added to the buffer
 | 
					
						
							|  |  |  |                         if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) { | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                             vt_apply_style({ Style::Foreground(Style::Color::Blue) }); | 
					
						
							|  |  |  |                             fflush(stdout); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                         if (spans_entire_line) { | 
					
						
							|  |  |  |                             num_printed += m_num_columns; | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             fprintf(stderr, "%s", suggestion.text.characters()); | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                         } else { | 
					
						
							| 
									
										
										
										
											2020-04-19 18:32:28 +04:30
										 |  |  |                             num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.text.characters()); | 
					
						
							| 
									
										
										
										
											2020-04-14 22:18:56 +04:30
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                         if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) { | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                             vt_apply_style({}); | 
					
						
							|  |  |  |                             fflush(stdout); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         ++index; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                     m_lines_used_for_last_suggestions = lines_used; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |                     // adjust for the case that we scroll up after writing the suggestions
 | 
					
						
							|  |  |  |                     if (m_origin_x + lines_used >= m_num_lines) { | 
					
						
							|  |  |  |                         m_origin_x = m_num_lines - lines_used; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     reposition_cursor(); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-04-12 22:24:21 +04:30
										 |  |  |                 if (m_suggestions.size() < 2) { | 
					
						
							|  |  |  |                     // we have none, or just one suggestion
 | 
					
						
							|  |  |  |                     // we should just commit that and continue
 | 
					
						
							|  |  |  |                     // after it, as if it were auto-completed
 | 
					
						
							|  |  |  |                     suggest(0, 0); | 
					
						
							|  |  |  |                     m_last_shown_suggestion = String::empty(); | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                     m_last_shown_suggestion_display_length = 0; | 
					
						
							| 
									
										
										
										
											2020-04-12 22:24:21 +04:30
										 |  |  |                     m_suggestions.clear(); | 
					
						
							|  |  |  |                     m_times_tab_pressed = 0; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |             if (m_times_tab_pressed) { | 
					
						
							|  |  |  |                 // we probably have some suggestions drawn
 | 
					
						
							|  |  |  |                 // let's clean them up
 | 
					
						
							|  |  |  |                 if (m_lines_used_for_last_suggestions) { | 
					
						
							| 
									
										
										
										
											2020-04-11 17:22:24 +04:30
										 |  |  |                     vt_clear_lines(0, m_lines_used_for_last_suggestions); | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                     reposition_cursor(); | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |                     m_refresh_needed = true; | 
					
						
							|  |  |  |                     m_lines_used_for_last_suggestions = 0; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |                 m_last_shown_suggestion_display_length = 0; | 
					
						
							| 
									
										
										
										
											2020-04-11 19:02:15 +04:30
										 |  |  |                 m_last_shown_suggestion = String::empty(); | 
					
						
							|  |  |  |                 m_suggestions.clear(); | 
					
						
							|  |  |  |                 suggest(0, 0); | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |             m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auto do_backspace = [&] { | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                 if (m_is_searching) { | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 if (m_cursor == 0) { | 
					
						
							|  |  |  |                     fputc('\a', stdout); | 
					
						
							|  |  |  |                     fflush(stdout); | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 m_buffer.remove(m_cursor - 1); | 
					
						
							|  |  |  |                 --m_cursor; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                 m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                 // we will have to redraw :(
 | 
					
						
							|  |  |  |                 m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (ch == 8 || ch == m_termios.c_cc[VERASE]) { | 
					
						
							|  |  |  |                 do_backspace(); | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (ch == m_termios.c_cc[VWERASE]) { | 
					
						
							|  |  |  |                 bool has_seen_nonspace = false; | 
					
						
							|  |  |  |                 while (m_cursor > 0) { | 
					
						
							|  |  |  |                     if (isspace(m_buffer[m_cursor - 1])) { | 
					
						
							|  |  |  |                         if (has_seen_nonspace) | 
					
						
							|  |  |  |                             break; | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         has_seen_nonspace = true; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     do_backspace(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (ch == m_termios.c_cc[VKILL]) { | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                 for (size_t i = 0; i < m_cursor; ++i) | 
					
						
							|  |  |  |                     m_buffer.remove(0); | 
					
						
							|  |  |  |                 m_cursor = 0; | 
					
						
							|  |  |  |                 m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |             // ^L
 | 
					
						
							|  |  |  |             if (ch == 0xc) { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 printf("\033[3J\033[H\033[2J"); // Clear screen.
 | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                 vt_move_absolute(1, 1); | 
					
						
							| 
									
										
										
										
											2020-04-11 17:22:24 +04:30
										 |  |  |                 m_origin_x = 1; | 
					
						
							|  |  |  |                 m_origin_y = 1; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                 m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |             // ^A
 | 
					
						
							|  |  |  |             if (ch == 0x01) { | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |                 m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |             // ^R
 | 
					
						
							|  |  |  |             if (ch == 0x12) { | 
					
						
							|  |  |  |                 if (m_is_searching) { | 
					
						
							|  |  |  |                     // how did we get here?
 | 
					
						
							|  |  |  |                     ASSERT_NOT_REACHED(); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     m_is_searching = true; | 
					
						
							|  |  |  |                     m_search_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-04-20 17:53:24 +04:30
										 |  |  |                     m_pre_search_buffer.clear(); | 
					
						
							|  |  |  |                     for (auto ch : m_buffer) | 
					
						
							|  |  |  |                         m_pre_search_buffer.append(ch); | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                     m_pre_search_cursor = m_cursor; | 
					
						
							|  |  |  |                     m_search_editor = make<Editor>(true); // Has anyone seen 'Inception'?
 | 
					
						
							|  |  |  |                     m_search_editor->initialize(); | 
					
						
							|  |  |  |                     m_search_editor->on_display_refresh = [this](Editor& search_editor) { | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  |                         search(StringView { search_editor.buffer().data(), search_editor.buffer().size() }); | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                         refresh_display(); | 
					
						
							|  |  |  |                         return; | 
					
						
							|  |  |  |                     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // whenever the search editor gets a ^R, cycle between history entries
 | 
					
						
							|  |  |  |                     m_search_editor->register_character_input_callback(0x12, [this](Editor& search_editor) { | 
					
						
							|  |  |  |                         ++m_search_offset; | 
					
						
							|  |  |  |                         search_editor.m_refresh_needed = true; | 
					
						
							|  |  |  |                         return false; // Do not process this key event
 | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // whenever the search editor gets a backspace, cycle back between history entries
 | 
					
						
							|  |  |  |                     // unless we're at the zeroth entry, in which case, allow the deletion
 | 
					
						
							|  |  |  |                     m_search_editor->register_character_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) { | 
					
						
							|  |  |  |                         if (m_search_offset > 0) { | 
					
						
							|  |  |  |                             --m_search_offset; | 
					
						
							|  |  |  |                             search_editor.m_refresh_needed = true; | 
					
						
							|  |  |  |                             return false; // Do not process this key event
 | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         return true; | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // quit without clearing the current buffer
 | 
					
						
							|  |  |  |                     m_search_editor->register_character_input_callback('\t', [this](Editor& search_editor) { | 
					
						
							|  |  |  |                         search_editor.finish(); | 
					
						
							|  |  |  |                         m_reset_buffer_on_search_end = false; | 
					
						
							|  |  |  |                         return false; | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     printf("\n"); | 
					
						
							|  |  |  |                     fflush(stdout); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     auto search_prompt = "\x1b[32msearch:\x1b[0m "; | 
					
						
							|  |  |  |                     auto search_string = m_search_editor->get_line(search_prompt); | 
					
						
							| 
									
										
										
										
											2020-04-20 17:53:24 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                     m_search_editor = nullptr; | 
					
						
							|  |  |  |                     m_is_searching = false; | 
					
						
							|  |  |  |                     m_search_offset = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // manually cleanup the search line
 | 
					
						
							|  |  |  |                     reposition_cursor(); | 
					
						
							|  |  |  |                     vt_clear_lines(0, (search_string.length() + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     reposition_cursor(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (!m_reset_buffer_on_search_end || search_string.length() == 0) { | 
					
						
							|  |  |  |                         // if the entry was empty, or we purposely quit without a newline,
 | 
					
						
							|  |  |  |                         // do not return anything
 | 
					
						
							|  |  |  |                         // instead, just end the search
 | 
					
						
							|  |  |  |                         end_search(); | 
					
						
							|  |  |  |                         continue; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // return the string
 | 
					
						
							|  |  |  |                     finish(); | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |             // Normally ^D
 | 
					
						
							|  |  |  |             if (ch == m_termios.c_cc[VEOF]) { | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 if (m_buffer.is_empty()) { | 
					
						
							|  |  |  |                     printf("<EOF>\n"); | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                     if (!m_always_refresh) // this is a little off, but it'll do for now
 | 
					
						
							|  |  |  |                         exit(0); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 } | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-19 15:55:17 +04:30
										 |  |  |             // ^E
 | 
					
						
							|  |  |  |             if (ch == 0x05) { | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |                 m_cursor = m_buffer.size(); | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (ch == '\n') { | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |                 finish(); | 
					
						
							|  |  |  |                 continue; | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             insert(ch); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 17:20:31 +04:30
										 |  |  | bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int last_matching_offset = -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // do not search for empty strings
 | 
					
						
							|  |  |  |     if (allow_empty || phrase.length() > 0) { | 
					
						
							|  |  |  |         size_t search_offset = m_search_offset; | 
					
						
							|  |  |  |         for (size_t i = m_history_cursor; i > 0; --i) { | 
					
						
							|  |  |  |             auto contains = from_beginning ? m_history[i - 1].starts_with(phrase) : m_history[i - 1].contains(phrase); | 
					
						
							|  |  |  |             if (contains) { | 
					
						
							|  |  |  |                 last_matching_offset = i - 1; | 
					
						
							|  |  |  |                 if (search_offset == 0) | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 --search_offset; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (last_matching_offset == -1) { | 
					
						
							|  |  |  |             fputc('\a', stdout); | 
					
						
							|  |  |  |             fflush(stdout); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_buffer.clear(); | 
					
						
							|  |  |  |     m_cursor = 0; | 
					
						
							|  |  |  |     if (last_matching_offset >= 0) { | 
					
						
							|  |  |  |         insert(m_history[last_matching_offset]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // always needed
 | 
					
						
							|  |  |  |     m_refresh_needed = true; | 
					
						
							|  |  |  |     return last_matching_offset >= 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-11 14:06:46 +04:30
										 |  |  | void Editor::recalculate_origin() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // changing the columns can affect our origin if
 | 
					
						
							|  |  |  |     // the new size is smaller than our prompt, which would
 | 
					
						
							|  |  |  |     // cause said prompt to take up more space, so we should
 | 
					
						
							|  |  |  |     // compensate for that
 | 
					
						
							|  |  |  |     if (m_cached_prompt_length >= m_num_columns) { | 
					
						
							|  |  |  |         auto added_lines = (m_cached_prompt_length + 1) / m_num_columns - 1; | 
					
						
							|  |  |  |         m_origin_x += added_lines; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // we also need to recalculate our cursor position
 | 
					
						
							|  |  |  |     // but that will be calculated and applied at the next
 | 
					
						
							|  |  |  |     // refresh cycle
 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  | void Editor::cleanup() | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  |     vt_move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle); | 
					
						
							|  |  |  |     auto current_line = cursor_line(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vt_clear_lines(current_line - 1, num_lines() - current_line); | 
					
						
							|  |  |  |     vt_move_relative(-num_lines() + 1, -offset_in_line() - m_old_prompt_length - m_pending_chars.size() + m_chars_inserted_in_the_middle); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 23:34:58 +04:30
										 |  |  | void Editor::refresh_display() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |     auto has_cleaned_up = false; | 
					
						
							|  |  |  |     // someone changed the window size, figure it out
 | 
					
						
							|  |  |  |     // and react to it, we might need to redraw
 | 
					
						
							|  |  |  |     if (m_was_resized) { | 
					
						
							|  |  |  |         auto previous_num_columns = m_num_columns; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         struct winsize ws; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) { | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |             m_num_columns = 80; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |             m_num_lines = 25; | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |             m_num_columns = ws.ws_col; | 
					
						
							| 
									
										
										
										
											2020-04-11 13:29:55 +04:30
										 |  |  |             m_num_lines = ws.ws_row; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |         if (previous_num_columns != m_num_columns) { | 
					
						
							|  |  |  |             // we need to cleanup and redo everything
 | 
					
						
							|  |  |  |             m_cached_prompt_valid = false; | 
					
						
							|  |  |  |             m_refresh_needed = true; | 
					
						
							|  |  |  |             swap(previous_num_columns, m_num_columns); | 
					
						
							| 
									
										
										
										
											2020-04-11 14:06:46 +04:30
										 |  |  |             recalculate_origin(); | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |             cleanup(); | 
					
						
							|  |  |  |             swap(previous_num_columns, m_num_columns); | 
					
						
							|  |  |  |             has_cleaned_up = true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     // do not call hook on pure cursor movement
 | 
					
						
							|  |  |  |     if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) { | 
					
						
							|  |  |  |         // probably just moving around
 | 
					
						
							|  |  |  |         reposition_cursor(); | 
					
						
							|  |  |  |         m_cached_buffer_size = m_buffer.size(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     if (on_display_refresh) | 
					
						
							|  |  |  |         on_display_refresh(*this); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     if (m_cached_prompt_valid) { | 
					
						
							|  |  |  |         if (!m_refresh_needed && m_cursor == m_buffer.size()) { | 
					
						
							|  |  |  |             // just write the characters out and continue
 | 
					
						
							|  |  |  |             // no need to refresh the entire line
 | 
					
						
							|  |  |  |             char null = 0; | 
					
						
							|  |  |  |             m_pending_chars.append(&null, 1); | 
					
						
							|  |  |  |             fputs((char*)m_pending_chars.data(), stdout); | 
					
						
							|  |  |  |             m_pending_chars.clear(); | 
					
						
							|  |  |  |             m_drawn_cursor = m_cursor; | 
					
						
							|  |  |  |             m_cached_buffer_size = m_buffer.size(); | 
					
						
							|  |  |  |             fflush(stdout); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // ouch, reflow entire line
 | 
					
						
							|  |  |  |     // FIXME: handle multiline stuff
 | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |     if (!has_cleaned_up) { | 
					
						
							|  |  |  |         cleanup(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-11 17:22:24 +04:30
										 |  |  |     vt_move_absolute(m_origin_x, m_origin_y); | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     fputs(m_new_prompt.characters(), stdout); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     vt_clear_to_end_of_line(); | 
					
						
							|  |  |  |     HashMap<u32, Style> empty_styles {}; | 
					
						
							|  |  |  |     for (size_t i = 0; i < m_buffer.size(); ++i) { | 
					
						
							|  |  |  |         auto ends = m_spans_ending.get(i).value_or(empty_styles); | 
					
						
							|  |  |  |         auto starts = m_spans_starting.get(i).value_or(empty_styles); | 
					
						
							|  |  |  |         if (ends.size()) { | 
					
						
							|  |  |  |             // go back to defaults
 | 
					
						
							|  |  |  |             vt_apply_style(find_applicable_style(i)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (starts.size()) { | 
					
						
							|  |  |  |             // set new options
 | 
					
						
							|  |  |  |             vt_apply_style(starts.begin()->value); // apply some random style that starts here
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         fputc(m_buffer[i], stdout); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     vt_apply_style({}); // don't bleed to EOL
 | 
					
						
							|  |  |  |     m_pending_chars.clear(); | 
					
						
							|  |  |  |     m_refresh_needed = false; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     m_cached_buffer_size = m_buffer.size(); | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     m_chars_inserted_in_the_middle = 0; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     if (!m_cached_prompt_valid) { | 
					
						
							|  |  |  |         m_cached_prompt_valid = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     reposition_cursor(); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::reposition_cursor() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_drawn_cursor = m_cursor; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto line = cursor_line() - 1; | 
					
						
							|  |  |  |     auto column = offset_in_line(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     vt_move_absolute(line + m_origin_x, column + m_origin_y); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::vt_move_absolute(u32 x, u32 y) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     printf("\033[%d;%dH", x, y); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::vt_move_relative(int x, int y) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     char x_op = 'A', y_op = 'D'; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  |     if (x > 0) | 
					
						
							|  |  |  |         x_op = 'B'; | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         x = -x; | 
					
						
							|  |  |  |     if (y > 0) | 
					
						
							|  |  |  |         y_op = 'C'; | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         y = -y; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (x > 0) | 
					
						
							|  |  |  |         printf("\033[%d%c", x, x_op); | 
					
						
							|  |  |  |     if (y > 0) | 
					
						
							|  |  |  |         printf("\033[%d%c", y, y_op); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Style Editor::find_applicable_style(size_t offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // walk through our styles and find one that fits in the offset
 | 
					
						
							|  |  |  |     for (auto& entry : m_spans_starting) { | 
					
						
							|  |  |  |         if (entry.key > offset) | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         for (auto& style_value : entry.value) { | 
					
						
							|  |  |  |             if (style_value.key <= offset) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             return style_value.value; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::vt_apply_style(const Style& style) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     printf( | 
					
						
							|  |  |  |         "\033[%d;%d;%d;%d;%dm", | 
					
						
							|  |  |  |         style.bold() ? 1 : 22, | 
					
						
							|  |  |  |         style.underline() ? 4 : 24, | 
					
						
							|  |  |  |         style.italic() ? 3 : 23, | 
					
						
							|  |  |  |         (int)style.foreground() + 30, | 
					
						
							|  |  |  |         (int)style.background() + 40); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::vt_clear_lines(size_t count_above, size_t count_below) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // go down count_below lines
 | 
					
						
							|  |  |  |     if (count_below > 0) | 
					
						
							|  |  |  |         printf("\033[%dB", (int)count_below); | 
					
						
							|  |  |  |     // then clear lines going upwards
 | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |     for (size_t i = count_below + count_above; i > 0; --i) | 
					
						
							|  |  |  |         fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stdout); | 
					
						
							| 
									
										
										
										
											2020-04-05 06:41:33 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::vt_save_cursor() | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     fputs("\033[s", stdout); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::vt_restore_cursor() | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     fputs("\033[u", stdout); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | void Editor::vt_clear_to_end_of_line() | 
					
						
							| 
									
										
										
										
											2020-03-30 21:29:04 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     fputs("\033[K", stdout); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | size_t Editor::actual_rendered_string_length(const StringView& string) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     size_t length { 0 }; | 
					
						
							|  |  |  |     enum VTState { | 
					
						
							|  |  |  |         Free = 1, | 
					
						
							|  |  |  |         Escape = 3, | 
					
						
							|  |  |  |         Bracket = 5, | 
					
						
							|  |  |  |         BracketArgsSemi = 7, | 
					
						
							|  |  |  |         Title = 9, | 
					
						
							|  |  |  |     } state { Free }; | 
					
						
							|  |  |  |     for (size_t i = 0; i < string.length(); ++i) { | 
					
						
							|  |  |  |         auto c = string[i]; | 
					
						
							|  |  |  |         switch (state) { | 
					
						
							|  |  |  |         case Free: | 
					
						
							|  |  |  |             if (c == '\x1b') { | 
					
						
							|  |  |  |                 // escape
 | 
					
						
							|  |  |  |                 state = Escape; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             // FIXME: This will not support anything sophisticated
 | 
					
						
							|  |  |  |             ++length; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case Escape: | 
					
						
							|  |  |  |             if (c == ']') { | 
					
						
							|  |  |  |                 if (string.length() > i && string[i + 1] == '0') | 
					
						
							|  |  |  |                     state = Title; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (c == '[') { | 
					
						
							|  |  |  |                 state = Bracket; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             // FIXME: This does not support non-VT (aside from set-title) escapes
 | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case Bracket: | 
					
						
							|  |  |  |             if (isdigit(c)) { | 
					
						
							|  |  |  |                 state = BracketArgsSemi; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case BracketArgsSemi: | 
					
						
							|  |  |  |             if (c == ';') { | 
					
						
							|  |  |  |                 state = Bracket; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (!isdigit(c)) | 
					
						
							|  |  |  |                 state = Free; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case Title: | 
					
						
							|  |  |  |             if (c == 7) | 
					
						
							|  |  |  |                 state = Free; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return length; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Vector<size_t, 2> Editor::vt_dsr() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     fputs("\033[6n", stdout); | 
					
						
							|  |  |  |     fflush(stdout); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     char buf[16]; | 
					
						
							|  |  |  |     u32 length { 0 }; | 
					
						
							|  |  |  |     do { | 
					
						
							|  |  |  |         auto nread = read(0, buf + length, 16 - length); | 
					
						
							|  |  |  |         if (nread < 0) { | 
					
						
							| 
									
										
										
										
											2020-04-10 12:58:27 +02:00
										 |  |  |             if (errno == 0) { | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |                 // ????
 | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |             dbg() << "Error while reading DSR: " << strerror(errno); | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |             return { 1, 1 }; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |         if (nread == 0) { | 
					
						
							|  |  |  |             dbg() << "Terminal DSR issue; received no response"; | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |             return { 1, 1 }; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |         length += nread; | 
					
						
							|  |  |  |     } while (buf[length - 1] != 'R' && length < 16); | 
					
						
							| 
									
										
										
										
											2020-04-10 09:53:22 +04:30
										 |  |  |     size_t x { 1 }, y { 1 }; | 
					
						
							| 
									
										
										
										
											2020-04-09 07:44:04 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     if (buf[0] == '\033' && buf[1] == '[') { | 
					
						
							|  |  |  |         auto parts = StringView(buf + 2, length - 3).split_view(';'); | 
					
						
							|  |  |  |         bool ok; | 
					
						
							|  |  |  |         x = parts[0].to_int(ok); | 
					
						
							|  |  |  |         if (!ok) { | 
					
						
							|  |  |  |             dbg() << "Terminal DSR issue; received garbage x"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         y = parts[1].to_int(ok); | 
					
						
							|  |  |  |         if (!ok) { | 
					
						
							|  |  |  |             dbg() << "Terminal DSR issue; received garbage y"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return { x, y }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-03-31 13:34:06 +02:00
										 |  |  | } |