| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2020, the SerenityOS developers. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-04-22 01:24:48 -07:00
										 |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  | #include <AK/CharacterTypes.h>
 | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  | #include <AK/ScopeGuard.h>
 | 
					
						
							| 
									
										
										
										
											2021-03-12 17:29:37 +01:00
										 |  |  | #include <AK/ScopedValueRollback.h>
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | #include <AK/StringBuilder.h>
 | 
					
						
							| 
									
										
										
										
											2021-02-24 09:33:26 +03:30
										 |  |  | #include <AK/TemporaryChange.h>
 | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  | #include <LibCore/File.h>
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | #include <LibLine/Editor.h>
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  | #include <sys/wait.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | namespace { | 
					
						
							|  |  |  | constexpr u32 ctrl(char c) { return c & 0x3f; } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Line { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-11 00:55:02 +01:00
										 |  |  | Function<bool(Editor&)> Editor::find_internal_function(StringView name) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | { | 
					
						
							|  |  |  | #define __ENUMERATE(internal_name) \
 | 
					
						
							|  |  |  |     if (name == #internal_name)    \ | 
					
						
							|  |  |  |         return EDITOR_INTERNAL_FUNCTION(internal_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ENUMERATE_EDITOR_INTERNAL_FUNCTIONS(__ENUMERATE) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 16:29:28 -07:00
										 |  |  |     return {}; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::search_forwards() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor }; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     StringBuilder builder; | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor }); | 
					
						
							| 
									
										
										
										
											2023-01-26 18:58:09 +00:00
										 |  |  |     auto search_phrase = builder.to_deprecated_string(); | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     if (m_search_offset_state == SearchOffsetState::Backwards) | 
					
						
							|  |  |  |         --m_search_offset; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     if (m_search_offset > 0) { | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |         ScopedValueRollback search_offset_rollback { m_search_offset }; | 
					
						
							|  |  |  |         --m_search_offset; | 
					
						
							|  |  |  |         if (search(search_phrase, true)) { | 
					
						
							|  |  |  |             m_search_offset_state = SearchOffsetState::Forwards; | 
					
						
							|  |  |  |             search_offset_rollback.set_override_rollback_value(m_search_offset); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             m_search_offset_state = SearchOffsetState::Unbiased; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |         m_search_offset_state = SearchOffsetState::Unbiased; | 
					
						
							| 
									
										
										
										
											2021-02-21 04:03:43 +03:30
										 |  |  |         m_chars_touched_in_the_middle = buffer().size(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         m_cursor = 0; | 
					
						
							|  |  |  |         m_buffer.clear(); | 
					
						
							|  |  |  |         insert(search_phrase); | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::search_backwards() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     ScopedValueRollback inline_search_cursor_rollback { m_inline_search_cursor }; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     StringBuilder builder; | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     builder.append(Utf32View { m_buffer.data(), m_inline_search_cursor }); | 
					
						
							| 
									
										
										
										
											2023-01-26 18:58:09 +00:00
										 |  |  |     auto search_phrase = builder.to_deprecated_string(); | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |     if (m_search_offset_state == SearchOffsetState::Forwards) | 
					
						
							|  |  |  |         ++m_search_offset; | 
					
						
							|  |  |  |     if (search(search_phrase, true)) { | 
					
						
							|  |  |  |         m_search_offset_state = SearchOffsetState::Backwards; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         ++m_search_offset; | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |         m_search_offset_state = SearchOffsetState::Unbiased; | 
					
						
							|  |  |  |         --m_search_offset; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::cursor_left_word() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_cursor > 0) { | 
					
						
							|  |  |  |         auto skipped_at_least_one_character = false; | 
					
						
							|  |  |  |         for (;;) { | 
					
						
							|  |  |  |             if (m_cursor == 0) | 
					
						
							|  |  |  |                 break; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |             if (skipped_at_least_one_character && !is_ascii_alphanumeric(m_buffer[m_cursor - 1])) // stop *after* a non-alnum, but only if it changes the position
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |                 break; | 
					
						
							|  |  |  |             skipped_at_least_one_character = true; | 
					
						
							|  |  |  |             --m_cursor; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::cursor_left_character() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-12-15 15:01:58 +03:30
										 |  |  |     if (m_cursor > 0) { | 
					
						
							|  |  |  |         size_t closest_cursor_left_offset; | 
					
						
							|  |  |  |         binary_search(m_cached_buffer_metrics.grapheme_breaks, m_cursor - 1, &closest_cursor_left_offset); | 
					
						
							|  |  |  |         m_cursor = m_cached_buffer_metrics.grapheme_breaks[closest_cursor_left_offset]; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::cursor_right_word() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_cursor < m_buffer.size()) { | 
					
						
							|  |  |  |         // Temporarily put a space at the end of our buffer,
 | 
					
						
							|  |  |  |         // doing this greatly simplifies the logic below.
 | 
					
						
							|  |  |  |         m_buffer.append(' '); | 
					
						
							|  |  |  |         for (;;) { | 
					
						
							|  |  |  |             if (m_cursor >= m_buffer.size()) | 
					
						
							|  |  |  |                 break; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |             if (!is_ascii_alphanumeric(m_buffer[++m_cursor])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |                 break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         m_buffer.take_last(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |     m_search_offset = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::cursor_right_character() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_cursor < m_buffer.size()) { | 
					
						
							| 
									
										
										
										
											2023-12-15 15:01:58 +03:30
										 |  |  |         size_t closest_cursor_left_offset; | 
					
						
							|  |  |  |         binary_search(m_cached_buffer_metrics.grapheme_breaks, m_cursor, &closest_cursor_left_offset); | 
					
						
							|  |  |  |         m_cursor = closest_cursor_left_offset + 1 >= m_cached_buffer_metrics.grapheme_breaks.size() | 
					
						
							|  |  |  |             ? m_buffer.size() | 
					
						
							|  |  |  |             : m_cached_buffer_metrics.grapheme_breaks[closest_cursor_left_offset + 1]; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } | 
					
						
							|  |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |     m_search_offset = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_character_backwards() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_is_searching) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (m_cursor == 0) { | 
					
						
							|  |  |  |         fputc('\a', stderr); | 
					
						
							|  |  |  |         fflush(stderr); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-12-15 15:01:58 +03:30
										 |  |  | 
 | 
					
						
							|  |  |  |     size_t closest_cursor_left_offset; | 
					
						
							|  |  |  |     binary_search(m_cached_buffer_metrics.grapheme_breaks, m_cursor - 1, &closest_cursor_left_offset); | 
					
						
							|  |  |  |     auto start_of_previous_grapheme = m_cached_buffer_metrics.grapheme_breaks[closest_cursor_left_offset]; | 
					
						
							|  |  |  |     for (; m_cursor > start_of_previous_grapheme; --m_cursor) | 
					
						
							|  |  |  |         remove_at_index(m_cursor - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |     // We will have to redraw :(
 | 
					
						
							|  |  |  |     m_refresh_needed = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_character_forwards() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_cursor == m_buffer.size()) { | 
					
						
							|  |  |  |         fputc('\a', stderr); | 
					
						
							|  |  |  |         fflush(stderr); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-12-15 15:01:58 +03:30
										 |  |  | 
 | 
					
						
							|  |  |  |     size_t closest_cursor_left_offset; | 
					
						
							|  |  |  |     binary_search(m_cached_buffer_metrics.grapheme_breaks, m_cursor, &closest_cursor_left_offset); | 
					
						
							|  |  |  |     auto end_of_next_grapheme = closest_cursor_left_offset + 1 >= m_cached_buffer_metrics.grapheme_breaks.size() | 
					
						
							|  |  |  |         ? m_buffer.size() | 
					
						
							|  |  |  |         : m_cached_buffer_metrics.grapheme_breaks[closest_cursor_left_offset + 1]; | 
					
						
							|  |  |  |     for (; m_cursor < end_of_next_grapheme;) | 
					
						
							|  |  |  |         remove_at_index(m_cursor); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     m_refresh_needed = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::finish_edit() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     fprintf(stderr, "<EOF>\n"); | 
					
						
							|  |  |  |     if (!m_always_refresh) { | 
					
						
							|  |  |  |         m_input_error = Error::Eof; | 
					
						
							|  |  |  |         finish(); | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |         really_quit_event_loop().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::kill_line() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     for (size_t i = 0; i < m_cursor; ++i) | 
					
						
							|  |  |  |         remove_at_index(0); | 
					
						
							|  |  |  |     m_cursor = 0; | 
					
						
							| 
									
										
										
										
											2022-02-25 23:29:40 -05:00
										 |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     m_refresh_needed = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_word_backwards() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // A word here is space-separated. `foo=bar baz` is two words.
 | 
					
						
							|  |  |  |     bool has_seen_nonspace = false; | 
					
						
							|  |  |  |     while (m_cursor > 0) { | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |         if (is_ascii_space(m_buffer[m_cursor - 1])) { | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             if (has_seen_nonspace) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             has_seen_nonspace = true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         erase_character_backwards(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_to_end() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     while (m_cursor < m_buffer.size()) | 
					
						
							|  |  |  |         erase_character_forwards(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_to_beginning() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::transpose_characters() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_cursor > 0 && m_buffer.size() >= 2) { | 
					
						
							|  |  |  |         if (m_cursor < m_buffer.size()) | 
					
						
							|  |  |  |             ++m_cursor; | 
					
						
							|  |  |  |         swap(m_buffer[m_cursor - 1], m_buffer[m_cursor - 2]); | 
					
						
							|  |  |  |         // FIXME: Update anchored styles too.
 | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2021-02-21 04:03:43 +03:30
										 |  |  |         m_chars_touched_in_the_middle += 2; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::enter_search() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_is_searching) { | 
					
						
							|  |  |  |         // How did we get here?
 | 
					
						
							| 
									
										
										
										
											2021-02-23 20:42:32 +01:00
										 |  |  |         VERIFY_NOT_REACHED(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } else { | 
					
						
							|  |  |  |         m_is_searching = true; | 
					
						
							|  |  |  |         m_search_offset = 0; | 
					
						
							|  |  |  |         m_pre_search_buffer.clear(); | 
					
						
							|  |  |  |         for (auto code_point : m_buffer) | 
					
						
							|  |  |  |             m_pre_search_buffer.append(code_point); | 
					
						
							|  |  |  |         m_pre_search_cursor = m_cursor; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-16 09:45:22 +04:30
										 |  |  |         ensure_free_lines_from_origin(1 + num_lines()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         // Disable our own notifier so as to avoid interfering with the search editor.
 | 
					
						
							|  |  |  |         m_notifier->set_enabled(false); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-09 03:40:07 +03:30
										 |  |  |         m_search_editor = Editor::construct(Configuration { Configuration::Eager, Configuration::NoSignalHandlers }); // Has anyone seen 'Inception'?
 | 
					
						
							| 
									
										
										
										
											2020-08-20 08:07:27 +04:30
										 |  |  |         m_search_editor->initialize(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         add_child(*m_search_editor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_search_editor->on_display_refresh = [this](Editor& search_editor) { | 
					
						
							| 
									
										
										
										
											2021-02-21 04:38:36 +03:30
										 |  |  |             // Remove the search editor prompt before updating ourselves (this avoids artifacts when we move the search editor around).
 | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |             search_editor.cleanup().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-02-21 04:38:36 +03:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             StringBuilder builder; | 
					
						
							|  |  |  |             builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() }); | 
					
						
							| 
									
										
										
										
											2023-01-26 18:58:09 +00:00
										 |  |  |             if (!search(builder.to_deprecated_string(), false, false)) { | 
					
						
							| 
									
										
										
										
											2021-02-21 04:03:43 +03:30
										 |  |  |                 m_chars_touched_in_the_middle = m_buffer.size(); | 
					
						
							|  |  |  |                 m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-08-23 17:44:07 +04:30
										 |  |  |                 m_buffer.clear(); | 
					
						
							|  |  |  |                 m_cursor = 0; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-02-21 04:38:36 +03:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |             refresh_display().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-02-21 04:38:36 +03:30
										 |  |  | 
 | 
					
						
							|  |  |  |             // Move the search prompt below ours and tell it to redraw itself.
 | 
					
						
							|  |  |  |             auto prompt_end_line = current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns); | 
					
						
							| 
									
										
										
										
											2021-02-24 09:33:26 +03:30
										 |  |  |             search_editor.set_origin(prompt_end_line + m_origin_row, 1); | 
					
						
							| 
									
										
										
										
											2021-02-21 04:38:36 +03:30
										 |  |  |             search_editor.m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Whenever the search editor gets a ^R, cycle between history entries.
 | 
					
						
							|  |  |  |         m_search_editor->register_key_input_callback(ctrl('R'), [this](Editor& search_editor) { | 
					
						
							|  |  |  |             ++m_search_offset; | 
					
						
							|  |  |  |             search_editor.m_refresh_needed = true; | 
					
						
							|  |  |  |             return false; // Do not process this key event
 | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-07 03:12:17 +03:30
										 |  |  |         // ^C should cancel the search.
 | 
					
						
							|  |  |  |         m_search_editor->register_key_input_callback(ctrl('C'), [this](Editor& search_editor) { | 
					
						
							|  |  |  |             search_editor.finish(); | 
					
						
							|  |  |  |             m_reset_buffer_on_search_end = true; | 
					
						
							| 
									
										
										
										
											2021-06-03 22:50:42 -04:00
										 |  |  |             search_editor.end_search(); | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |             search_editor.deferred_invoke([&search_editor] { search_editor.really_quit_event_loop().release_value_but_fixme_should_propagate_errors(); }); | 
					
						
							| 
									
										
										
										
											2021-02-07 03:12:17 +03:30
										 |  |  |             return false; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         // 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_key_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
 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-20 08:07:27 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |             search_editor.erase_character_backwards(); | 
					
						
							|  |  |  |             return false; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // ^L - This is a source of issues, as the search editor refreshes first,
 | 
					
						
							|  |  |  |         // and we end up with the wrong order of prompts, so we will first refresh
 | 
					
						
							|  |  |  |         // ourselves, then refresh the search editor, and then tell him not to process
 | 
					
						
							|  |  |  |         // this event.
 | 
					
						
							|  |  |  |         m_search_editor->register_key_input_callback(ctrl('L'), [this](auto& search_editor) { | 
					
						
							|  |  |  |             fprintf(stderr, "\033[3J\033[H\033[2J"); // Clear screen.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // refresh our own prompt
 | 
					
						
							| 
									
										
										
										
											2021-02-24 09:33:26 +03:30
										 |  |  |             { | 
					
						
							|  |  |  |                 TemporaryChange refresh_change { m_always_refresh, true }; | 
					
						
							|  |  |  |                 set_origin(1, 1); | 
					
						
							|  |  |  |                 m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |                 refresh_display().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-02-24 09:33:26 +03:30
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |             // move the search prompt below ours
 | 
					
						
							|  |  |  |             // and tell it to redraw itself
 | 
					
						
							| 
									
										
										
										
											2021-02-07 03:14:42 +03:30
										 |  |  |             auto prompt_end_line = current_prompt_metrics().lines_with_addition(m_cached_buffer_metrics, m_num_columns); | 
					
						
							|  |  |  |             search_editor.set_origin(prompt_end_line + 1, 1); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             search_editor.m_refresh_needed = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // quit without clearing the current buffer
 | 
					
						
							|  |  |  |         m_search_editor->register_key_input_callback('\t', [this](Editor& search_editor) { | 
					
						
							|  |  |  |             search_editor.finish(); | 
					
						
							|  |  |  |             m_reset_buffer_on_search_end = false; | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-11 17:32:29 +00:00
										 |  |  |         auto search_prompt = "\x1b[32msearch:\x1b[0m "sv; | 
					
						
							| 
									
										
										
										
											2020-09-07 00:26:28 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |         // While the search editor is active, we do not want editing events.
 | 
					
						
							|  |  |  |         m_is_editing = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         auto search_string_result = m_search_editor->get_line(search_prompt); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-07 00:26:28 +04:30
										 |  |  |         // Grab where the search origin last was, anything up to this point will be cleared.
 | 
					
						
							|  |  |  |         auto search_end_row = m_search_editor->m_origin_row; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         remove_child(*m_search_editor); | 
					
						
							|  |  |  |         m_search_editor = nullptr; | 
					
						
							|  |  |  |         m_is_searching = false; | 
					
						
							| 
									
										
										
										
											2020-09-07 00:26:28 +04:30
										 |  |  |         m_is_editing = true; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         m_search_offset = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Re-enable the notifier after discarding the search editor.
 | 
					
						
							|  |  |  |         m_notifier->set_enabled(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (search_string_result.is_error()) { | 
					
						
							|  |  |  |             // Somethine broke, fail
 | 
					
						
							|  |  |  |             m_input_error = search_string_result.error(); | 
					
						
							|  |  |  |             finish(); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto& search_string = search_string_result.value(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Manually cleanup the search line.
 | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |         auto stderr_stream = Core::File::standard_error().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |         reposition_cursor(*stderr_stream).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2022-05-02 16:10:42 +04:30
										 |  |  |         auto search_metrics = actual_rendered_string_metrics(search_string, {}); | 
					
						
							|  |  |  |         auto metrics = actual_rendered_string_metrics(search_prompt, {}); | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |         VT::clear_lines(0, metrics.lines_with_addition(search_metrics, m_num_columns) + search_end_row - m_origin_row - 1, *stderr_stream).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |         reposition_cursor(*stderr_stream).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-16 09:45:22 +04:30
										 |  |  |         m_refresh_needed = true; | 
					
						
							|  |  |  |         m_cached_prompt_valid = false; | 
					
						
							|  |  |  |         m_chars_touched_in_the_middle = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         if (!m_reset_buffer_on_search_end || search_metrics.total_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(); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Return the string,
 | 
					
						
							|  |  |  |         finish(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::transpose_words() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // A word here is contiguous alnums. `foo=bar baz` is three words.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 'abcd,.:efg...' should become 'efg...,.:abcd' if caret is after
 | 
					
						
							|  |  |  |     // 'efg...'. If it's in 'efg', it should become 'efg,.:abcd...'
 | 
					
						
							|  |  |  |     // with the caret after it, which then becomes 'abcd...,.:efg'
 | 
					
						
							|  |  |  |     // when alt-t is pressed a second time.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Move to end of word under (or after) caret.
 | 
					
						
							|  |  |  |     size_t cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (cursor < m_buffer.size() && !is_ascii_alphanumeric(m_buffer[cursor])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         ++cursor; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (cursor < m_buffer.size() && is_ascii_alphanumeric(m_buffer[cursor])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         ++cursor; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Move left over second word and the space to its right.
 | 
					
						
							|  |  |  |     size_t end = cursor; | 
					
						
							|  |  |  |     size_t start = cursor; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (start > 0 && !is_ascii_alphanumeric(m_buffer[start - 1])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         --start; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (start > 0 && is_ascii_alphanumeric(m_buffer[start - 1])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         --start; | 
					
						
							|  |  |  |     size_t start_second_word = start; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Move left over space between the two words.
 | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (start > 0 && !is_ascii_alphanumeric(m_buffer[start - 1])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         --start; | 
					
						
							|  |  |  |     size_t start_gap = start; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Move left over first word.
 | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (start > 0 && is_ascii_alphanumeric(m_buffer[start - 1])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         --start; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (start != start_gap) { | 
					
						
							|  |  |  |         // To swap the two words, swap each word (and the gap) individually, and then swap the whole range.
 | 
					
						
							|  |  |  |         auto swap_range = [this](auto from, auto to) { | 
					
						
							|  |  |  |             for (size_t i = 0; i < (to - from) / 2; ++i) | 
					
						
							|  |  |  |                 swap(m_buffer[from + i], m_buffer[to - 1 - i]); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         swap_range(start, start_gap); | 
					
						
							|  |  |  |         swap_range(start_gap, start_second_word); | 
					
						
							|  |  |  |         swap_range(start_second_word, end); | 
					
						
							|  |  |  |         swap_range(start, end); | 
					
						
							|  |  |  |         m_cursor = cursor; | 
					
						
							|  |  |  |         // FIXME: Update anchored styles too.
 | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2021-02-21 04:03:43 +03:30
										 |  |  |         m_chars_touched_in_the_middle += end - start; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::go_home() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_cursor = 0; | 
					
						
							|  |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |     m_search_offset = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::go_end() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_cursor = m_buffer.size(); | 
					
						
							|  |  |  |     m_inline_search_cursor = m_cursor; | 
					
						
							|  |  |  |     m_search_offset = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::clear_screen() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |     warn("\033[3J\033[H\033[2J"); | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |     auto stream = Core::File::standard_error().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |     VT::move_absolute(1, 1, *stream).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |     set_origin(1, 1); | 
					
						
							|  |  |  |     m_refresh_needed = true; | 
					
						
							| 
									
										
										
										
											2021-02-21 04:03:43 +03:30
										 |  |  |     m_cached_prompt_valid = false; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::insert_last_words() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_history.is_empty()) { | 
					
						
							|  |  |  |         // FIXME: This isn't quite right: if the last arg was `"foo bar"` or `foo\ bar` (but not `foo\\ bar`), we should insert that whole arg as last token.
 | 
					
						
							| 
									
										
										
										
											2021-01-11 18:58:42 +03:30
										 |  |  |         if (auto last_words = m_history.last().entry.split_view(' '); !last_words.is_empty()) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             insert(last_words.last()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_alnum_word_backwards() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // A word here is contiguous alnums. `foo=bar baz` is three words.
 | 
					
						
							|  |  |  |     bool has_seen_alnum = false; | 
					
						
							|  |  |  |     while (m_cursor > 0) { | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |         if (!is_ascii_alphanumeric(m_buffer[m_cursor - 1])) { | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             if (has_seen_alnum) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             has_seen_alnum = true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         erase_character_backwards(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::erase_alnum_word_forwards() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // A word here is contiguous alnums. `foo=bar baz` is three words.
 | 
					
						
							|  |  |  |     bool has_seen_alnum = false; | 
					
						
							|  |  |  |     while (m_cursor < m_buffer.size()) { | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |         if (!is_ascii_alphanumeric(m_buffer[m_cursor])) { | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |             if (has_seen_alnum) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             has_seen_alnum = true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         erase_character_forwards(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::case_change_word(Editor::CaseChangeOp change_op) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // A word here is contiguous alnums. `foo=bar baz` is three words.
 | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (m_cursor < m_buffer.size() && !is_ascii_alphanumeric(m_buffer[m_cursor])) | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         ++m_cursor; | 
					
						
							|  |  |  |     size_t start = m_cursor; | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |     while (m_cursor < m_buffer.size() && is_ascii_alphanumeric(m_buffer[m_cursor])) { | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         if (change_op == CaseChangeOp::Uppercase || (change_op == CaseChangeOp::Capital && m_cursor == start)) { | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |             m_buffer[m_cursor] = to_ascii_uppercase(m_buffer[m_cursor]); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-02-23 20:42:32 +01:00
										 |  |  |             VERIFY(change_op == CaseChangeOp::Lowercase || (change_op == CaseChangeOp::Capital && m_cursor > start)); | 
					
						
							| 
									
										
										
										
											2021-06-01 21:18:08 +02:00
										 |  |  |             m_buffer[m_cursor] = to_ascii_lowercase(m_buffer[m_cursor]); | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |         ++m_cursor; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-08-04 16:31:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     m_refresh_needed = true; | 
					
						
							|  |  |  |     m_chars_touched_in_the_middle = 1; | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::capitalize_word() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     case_change_word(CaseChangeOp::Capital); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::lowercase_word() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     case_change_word(CaseChangeOp::Lowercase); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Editor::uppercase_word() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     case_change_word(CaseChangeOp::Uppercase); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  | void Editor::edit_in_external_editor() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2022-04-01 20:58:27 +03:00
										 |  |  |     auto const* editor_command = getenv("EDITOR"); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  |     if (!editor_command) | 
					
						
							|  |  |  |         editor_command = m_configuration.m_default_text_editor.characters(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     char file_path[] = "/tmp/line-XXXXXX"; | 
					
						
							|  |  |  |     auto fd = mkstemp(file_path); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (fd < 0) { | 
					
						
							|  |  |  |         perror("mktemp"); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-04-11 04:24:06 +04:30
										 |  |  |         auto write_fd = dup(fd); | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |         auto stream = Core::File::adopt_fd(write_fd, Core::File::OpenMode::Write).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  |         StringBuilder builder; | 
					
						
							|  |  |  |         builder.append(Utf32View { m_buffer.data(), m_buffer.size() }); | 
					
						
							| 
									
										
										
										
											2021-05-01 00:17:54 +04:30
										 |  |  |         auto bytes = builder.string_view().bytes(); | 
					
						
							|  |  |  |         while (!bytes.is_empty()) { | 
					
						
							| 
									
										
										
										
											2023-02-24 22:38:01 +01:00
										 |  |  |             auto nwritten = stream->write_some(bytes).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-05-01 00:17:54 +04:30
										 |  |  |             bytes = bytes.slice(nwritten); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-04-11 04:24:06 +04:30
										 |  |  |         lseek(fd, 0, SEEK_SET); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ScopeGuard remove_temp_file_guard { | 
					
						
							|  |  |  |         [fd, file_path] { | 
					
						
							|  |  |  |             close(fd); | 
					
						
							|  |  |  |             unlink(file_path); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 20:58:27 +03:00
										 |  |  |     Vector<char const*> args { editor_command, file_path, nullptr }; | 
					
						
							| 
									
										
										
										
											2022-01-07 00:35:25 -07:00
										 |  |  |     auto pid = fork(); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     if (pid == -1) { | 
					
						
							| 
									
										
										
										
											2022-01-07 00:35:25 -07:00
										 |  |  |         perror("fork"); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (pid == 0) { | 
					
						
							|  |  |  |         execvp(editor_command, const_cast<char* const*>(args.data())); | 
					
						
							|  |  |  |         perror("execv"); | 
					
						
							|  |  |  |         _exit(126); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         int wstatus = 0; | 
					
						
							|  |  |  |         do { | 
					
						
							|  |  |  |             waitpid(pid, &wstatus, 0); | 
					
						
							|  |  |  |         } while (errno == EINTR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |         auto file = Core::File::open({ file_path, strlen(file_path) }, Core::File::OpenMode::Read).release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-01-10 12:21:17 +01:00
										 |  |  |         auto contents = file->read_until_eof().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2021-04-17 21:41:38 +04:30
										 |  |  |         StringView data { contents }; | 
					
						
							|  |  |  |         while (data.ends_with('\n')) | 
					
						
							|  |  |  |             data = data.substring_view(0, data.length() - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_cursor = 0; | 
					
						
							|  |  |  |         m_chars_touched_in_the_middle = m_buffer.size(); | 
					
						
							|  |  |  |         m_buffer.clear_with_capacity(); | 
					
						
							|  |  |  |         m_refresh_needed = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Utf8View view { data }; | 
					
						
							|  |  |  |         if (view.validate()) { | 
					
						
							|  |  |  |             for (auto cp : view) | 
					
						
							|  |  |  |                 insert(cp); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             for (auto ch : data) | 
					
						
							|  |  |  |                 insert(ch); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-08-17 20:38:10 +04:30
										 |  |  | } |