Serenity Operating System
1/*
2 * Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
3 * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
4 * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
5 * Copyright (c) 2022, the SerenityOS developers.
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#define AK_DONT_REPLACE_STD
11
12#include "ConsoleWidget.h"
13#include "Utilities.h"
14#include <AK/StringBuilder.h>
15#include <LibJS/MarkupGenerator.h>
16#include <QLineEdit>
17#include <QPushButton>
18#include <QTextEdit>
19#include <QVBoxLayout>
20
21namespace Ladybird {
22
23ConsoleWidget::ConsoleWidget()
24{
25 setLayout(new QVBoxLayout);
26
27 m_output_view = new QTextEdit(this);
28 m_output_view->setReadOnly(true);
29 layout()->addWidget(m_output_view);
30
31 if (on_request_messages)
32 on_request_messages(0);
33
34 auto* bottom_container = new QWidget(this);
35 bottom_container->setLayout(new QHBoxLayout);
36
37 layout()->addWidget(bottom_container);
38
39 m_input = new QLineEdit(bottom_container);
40 bottom_container->layout()->addWidget(m_input);
41
42 QObject::connect(m_input, &QLineEdit::returnPressed, [this] {
43 auto js_source = ak_deprecated_string_from_qstring(m_input->text());
44
45 if (js_source.is_whitespace())
46 return;
47
48 m_input->clear();
49
50 print_source_line(js_source);
51
52 if (on_js_input)
53 on_js_input(js_source);
54 });
55
56 setFocusProxy(m_input);
57
58 auto* clear_button = new QPushButton(bottom_container);
59 bottom_container->layout()->addWidget(clear_button);
60 clear_button->setFixedSize(22, 22);
61 clear_button->setText("X");
62 clear_button->setToolTip("Clear the console output");
63 QObject::connect(clear_button, &QPushButton::pressed, [this] {
64 clear_output();
65 });
66
67 m_input->setFocus();
68}
69
70void ConsoleWidget::request_console_messages()
71{
72 VERIFY(!m_waiting_for_messages);
73 VERIFY(on_request_messages);
74 on_request_messages(m_highest_received_message_index + 1);
75 m_waiting_for_messages = true;
76}
77
78void ConsoleWidget::notify_about_new_console_message(i32 message_index)
79{
80 if (message_index <= m_highest_received_message_index) {
81 dbgln("Notified about console message we already have");
82 return;
83 }
84 if (message_index <= m_highest_notified_message_index) {
85 dbgln("Notified about console message we're already aware of");
86 return;
87 }
88
89 m_highest_notified_message_index = message_index;
90 if (!m_waiting_for_messages)
91 request_console_messages();
92}
93
94void ConsoleWidget::handle_console_messages(i32 start_index, Vector<DeprecatedString> const& message_types, Vector<DeprecatedString> const& messages)
95{
96 i32 end_index = start_index + message_types.size() - 1;
97 if (end_index <= m_highest_received_message_index) {
98 dbgln("Received old console messages");
99 return;
100 }
101
102 for (size_t i = 0; i < message_types.size(); i++) {
103 auto& type = message_types[i];
104 auto& message = messages[i];
105
106 if (type == "html") {
107 print_html(message);
108 } else if (type == "clear") {
109 clear_output();
110 } else if (type == "group") {
111 // FIXME: Implement.
112 } else if (type == "groupCollapsed") {
113 // FIXME: Implement.
114 } else if (type == "groupEnd") {
115 // FIXME: Implement.
116 } else {
117 VERIFY_NOT_REACHED();
118 }
119 }
120
121 m_highest_received_message_index = end_index;
122 m_waiting_for_messages = false;
123
124 if (m_highest_received_message_index < m_highest_notified_message_index)
125 request_console_messages();
126}
127
128void ConsoleWidget::print_source_line(StringView source)
129{
130 StringBuilder html;
131 html.append("<span class=\"repl-indicator\">"sv);
132 html.append("> "sv);
133 html.append("</span>"sv);
134
135 html.append(JS::MarkupGenerator::html_from_source(source).release_value_but_fixme_should_propagate_errors());
136
137 print_html(html.string_view());
138}
139
140void ConsoleWidget::print_html(StringView line)
141{
142 m_output_view->append(QString::fromUtf8(line.characters_without_null_termination(), line.length()));
143}
144
145void ConsoleWidget::clear_output()
146{
147 m_output_view->clear();
148}
149
150void ConsoleWidget::reset()
151{
152 clear_output();
153 m_highest_notified_message_index = -1;
154 m_highest_received_message_index = -1;
155 m_waiting_for_messages = false;
156}
157
158}