Serenity Operating System
at portability 198 lines 6.7 kB view raw
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#include "Editor.h" 28#include "EditorWrapper.h" 29#include <AK/ByteBuffer.h> 30#include <AK/FileSystemPath.h> 31#include <LibCore/DirIterator.h> 32#include <LibCore/File.h> 33#include <LibGUI/Application.h> 34#include <LibGUI/Painter.h> 35#include <LibGUI/ScrollBar.h> 36#include <LibGUI/Window.h> 37#include <LibHTML/DOM/ElementFactory.h> 38#include <LibHTML/DOM/HTMLHeadElement.h> 39#include <LibHTML/DOM/Text.h> 40#include <LibHTML/HtmlView.h> 41#include <LibHTML/Parser/HTMLParser.h> 42#include <LibMarkdown/MDDocument.h> 43 44//#define EDITOR_DEBUG 45 46Editor::Editor() 47{ 48 m_documentation_tooltip_window = GUI::Window::construct(); 49 m_documentation_tooltip_window->set_rect(0, 0, 500, 400); 50 m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip); 51 52 m_documentation_html_view = HtmlView::construct(); 53 m_documentation_tooltip_window->set_main_widget(m_documentation_html_view); 54} 55 56Editor::~Editor() 57{ 58} 59 60EditorWrapper& Editor::wrapper() 61{ 62 return static_cast<EditorWrapper&>(*parent()); 63} 64const EditorWrapper& Editor::wrapper() const 65{ 66 return static_cast<const EditorWrapper&>(*parent()); 67} 68 69void Editor::focusin_event(Core::Event& event) 70{ 71 wrapper().set_editor_has_focus({}, true); 72 if (on_focus) 73 on_focus(); 74 GUI::TextEditor::focusin_event(event); 75} 76 77void Editor::focusout_event(Core::Event& event) 78{ 79 wrapper().set_editor_has_focus({}, false); 80 GUI::TextEditor::focusout_event(event); 81} 82 83void Editor::paint_event(GUI::PaintEvent& event) 84{ 85 GUI::TextEditor::paint_event(event); 86 87 if (is_focused()) { 88 GUI::Painter painter(*this); 89 painter.add_clip_rect(event.rect()); 90 91 auto rect = frame_inner_rect(); 92 if (vertical_scrollbar().is_visible()) 93 rect.set_width(rect.width() - vertical_scrollbar().width()); 94 if (horizontal_scrollbar().is_visible()) 95 rect.set_height(rect.height() - horizontal_scrollbar().height()); 96 painter.draw_rect(rect, palette().selection()); 97 } 98} 99 100static HashMap<String, String>& man_paths() 101{ 102 static HashMap<String, String> paths; 103 if (paths.is_empty()) { 104 // FIXME: This should also search man3, possibly other places.. 105 Core::DirIterator it("/usr/share/man/man2", Core::DirIterator::Flags::SkipDots); 106 while (it.has_next()) { 107 auto path = String::format("/usr/share/man/man2/%s", it.next_path().characters()); 108 auto title = FileSystemPath(path).title(); 109 paths.set(title, path); 110 } 111 } 112 113 return paths; 114} 115 116void Editor::show_documentation_tooltip_if_available(const String& hovered_token, const Gfx::Point& screen_location) 117{ 118 auto it = man_paths().find(hovered_token); 119 if (it == man_paths().end()) { 120#ifdef EDITOR_DEBUG 121 dbg() << "no man path for " << hovered_token; 122#endif 123 m_documentation_tooltip_window->hide(); 124 return; 125 } 126 127 if (m_documentation_tooltip_window->is_visible() && hovered_token == m_last_parsed_token) { 128 return; 129 } 130 131#ifdef EDITOR_DEBUG 132 dbg() << "opening " << it->value; 133#endif 134 auto file = Core::File::construct(it->value); 135 if (!file->open(Core::File::ReadOnly)) { 136 dbg() << "failed to open " << it->value << " " << file->error_string(); 137 return; 138 } 139 140 MDDocument man_document; 141 bool success = man_document.parse(file->read_all()); 142 143 if (!success) { 144 dbg() << "failed to parse markdown"; 145 return; 146 } 147 148 auto html_text = man_document.render_to_html(); 149 150 auto html_document = parse_html_document(html_text); 151 if (!html_document) { 152 dbg() << "failed to parse HTML"; 153 return; 154 } 155 156 // FIXME: LibHTML needs a friendlier DOM manipulation API. Something like innerHTML :^) 157 auto style_element = create_element(*html_document, "style"); 158 style_element->append_child(adopt(*new Text(*html_document, "body { background-color: #dac7b5; }"))); 159 160 // FIXME: This const_cast should not be necessary. 161 auto* head_element = const_cast<HTMLHeadElement*>(html_document->head()); 162 ASSERT(head_element); 163 head_element->append_child(style_element); 164 165 m_documentation_html_view->set_document(html_document); 166 m_documentation_tooltip_window->move_to(screen_location.translated(4, 4)); 167 m_documentation_tooltip_window->show(); 168 169 m_last_parsed_token = hovered_token; 170} 171 172void Editor::mousemove_event(GUI::MouseEvent& event) 173{ 174 GUI::TextEditor::mousemove_event(event); 175 176 if (document().spans().is_empty()) 177 return; 178 179 auto text_position = text_position_at(event.position()); 180 if (!text_position.is_valid()) { 181 GUI::Application::the().hide_tooltip(); 182 return; 183 } 184 185 for (auto& span : document().spans()) { 186 if (span.range.contains(text_position)) { 187 auto adjusted_range = span.range; 188 adjusted_range.end().set_column(adjusted_range.end().column() + 1); 189 auto hovered_span_text = document().text_in_range(adjusted_range); 190#ifdef EDITOR_DEBUG 191 dbg() << "Hovering: " << adjusted_range << " \"" << hovered_span_text << "\""; 192#endif 193 show_documentation_tooltip_if_available(hovered_span_text, event.position().translated(screen_relative_rect().location())); 194 return; 195 } 196 } 197 GUI::Application::the().hide_tooltip(); 198}