Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#pragma once
28
29#include <AK/BinarySearch.h>
30#include <AK/ByteBuffer.h>
31#include <AK/FileSystemPath.h>
32#include <AK/Function.h>
33#include <AK/HashMap.h>
34#include <AK/NonnullOwnPtr.h>
35#include <AK/QuickSort.h>
36#include <AK/String.h>
37#include <AK/Vector.h>
38#include <LibCore/DirIterator.h>
39#include <Libraries/LibLine/Span.h>
40#include <Libraries/LibLine/Style.h>
41#include <sys/stat.h>
42#include <termios.h>
43
44namespace Line {
45
46class Editor;
47
48struct KeyCallback {
49 KeyCallback(Function<bool(Editor&)> cb)
50 : callback(move(cb))
51 {
52 }
53 Function<bool(Editor&)> callback;
54};
55
56class Editor {
57public:
58 Editor();
59 ~Editor();
60
61 void initialize()
62 {
63 ASSERT(!m_initialized);
64 struct termios termios;
65 tcgetattr(0, &termios);
66 m_default_termios = termios; // grab a copy to restore
67 // Because we use our own line discipline which includes echoing,
68 // we disable ICANON and ECHO.
69 termios.c_lflag &= ~(ECHO | ICANON);
70 tcsetattr(0, TCSANOW, &termios);
71 m_termios = termios;
72 m_initialized = true;
73 }
74
75 String get_line(const String& prompt);
76
77 void add_to_history(const String&);
78 const Vector<String>& history() const { return m_history; }
79
80 void register_character_input_callback(char ch, Function<bool(Editor&)> callback);
81
82 Function<Vector<String>(const String&)> on_tab_complete_first_token;
83 Function<Vector<String>(const String&)> on_tab_complete_other_token;
84 Function<void(Editor&)> on_display_refresh;
85
86 // FIXME: we will have to kindly ask our instantiators to set our signal handlers
87 // since we can not do this cleanly ourselves (signal() limitation: cannot give member functions)
88 void interrupted() { m_was_interrupted = true; }
89 void resized() { m_was_resized = true; }
90
91 size_t cursor() const { return m_cursor; }
92 const Vector<char, 1024>& buffer() const { return m_buffer; }
93 char buffer_at(size_t pos) const { return m_buffer.at(pos); }
94
95 void clear_line();
96 void insert(const String&);
97 void insert(const char);
98 void cut_mismatching_chars(String& completion, const String& other, size_t start_compare);
99 void stylize(const Span&, const Style&);
100 void strip_styles()
101 {
102 m_spans_starting.clear();
103 m_spans_ending.clear();
104 m_refresh_needed = true;
105 }
106
107 const struct termios& termios() const { return m_termios; }
108 const struct termios& default_termios() const { return m_default_termios; }
109
110private:
111 void vt_save_cursor();
112 void vt_restore_cursor();
113 void vt_clear_to_end_of_line();
114 void vt_clear_lines(size_t count_above, size_t count_below = 0);
115 void vt_move_relative(int x, int y);
116 void vt_apply_style(const Style&);
117
118 Style find_applicable_style(size_t offset) const;
119
120 void refresh_display();
121
122 // FIXME: These three will report the wrong value because they do not
123 // take the length of the prompt into consideration, and it does not
124 // appear that we can figure that out easily
125 size_t num_lines() const
126 {
127 return (m_buffer.size() + m_num_columns) / m_num_columns;
128 }
129
130 size_t cursor_line() const
131 {
132 return (m_cursor + m_num_columns) / m_num_columns;
133 }
134
135 size_t offset_in_line() const
136 {
137 auto offset = m_cursor % m_num_columns;
138 return offset;
139 }
140
141 Vector<char, 1024> m_buffer;
142 ByteBuffer m_pending_chars;
143 size_t m_cursor { 0 };
144 size_t m_chars_inserted_in_the_middle { 0 };
145 size_t m_times_tab_pressed { 0 };
146 size_t m_num_columns { 0 };
147
148 HashMap<char, NonnullOwnPtr<KeyCallback>> m_key_callbacks;
149
150 // TODO: handle signals internally
151 struct termios m_termios, m_default_termios;
152 bool m_was_interrupted = false;
153 bool m_was_resized = false;
154
155 // FIXME: This should be something more take_first()-friendly.
156 Vector<String> m_history;
157 size_t m_history_cursor { 0 };
158 size_t m_history_capacity { 100 };
159
160 enum class InputState {
161 Free,
162 ExpectBracket,
163 ExpectFinal,
164 ExpectTerminator,
165 };
166 InputState m_state { InputState::Free };
167
168 HashMap<u32, HashMap<u32, Style>> m_spans_starting;
169 HashMap<u32, HashMap<u32, Style>> m_spans_ending;
170
171 bool m_initialized { false };
172 bool m_refresh_needed { false };
173};
174
175}