diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-10-29 21:39:12 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-10-30 20:28:44 +0100 |
commit | 7c71040ba9e6adcf25cce07d718ef7b996dcfa50 (patch) | |
tree | c3b419e53f2679c642d177ee32c89d83200d5342 /DevTools/HackStudio | |
parent | a4709502d1b9a9b2635343e8b03d89a0b89815e6 (diff) | |
download | serenity-7c71040ba9e6adcf25cce07d718ef7b996dcfa50.zip |
HackStudio: Show documentation preview in tooltip on identifier hover
When hovering over a C++ token that we have a man page for, we now show
the man page in a tooltip window.
This feels rather bulky at the moment, but the basic mechanism is quite
neat and just needs a bunch of tuning.
Diffstat (limited to 'DevTools/HackStudio')
-rw-r--r-- | DevTools/HackStudio/Editor.cpp | 118 | ||||
-rw-r--r-- | DevTools/HackStudio/Editor.h | 14 | ||||
-rw-r--r-- | DevTools/HackStudio/Makefile | 2 |
3 files changed, 127 insertions, 7 deletions
diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index fe0666d48f..1ea850b781 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -1,7 +1,33 @@ #include "Editor.h" #include "EditorWrapper.h" +#include <AK/FileSystemPath.h> +#include <LibCore/CDirIterator.h> +#include <LibCore/CFile.h> +#include <LibGUI/GApplication.h> #include <LibGUI/GPainter.h> #include <LibGUI/GScrollBar.h> +#include <LibGUI/GWindow.h> +#include <LibHTML/DOM/ElementFactory.h> +#include <LibHTML/DOM/HTMLHeadElement.h> +#include <LibHTML/DOM/Text.h> +#include <LibHTML/HtmlView.h> +#include <LibHTML/Parser/HTMLParser.h> +#include <LibMarkdown/MDDocument.h> + +Editor::Editor(GWidget* parent) + : GTextEditor(GTextEditor::MultiLine, parent) +{ + m_documentation_tooltip_window = GWindow::construct(); + m_documentation_tooltip_window->set_rect(0, 0, 500, 400); + m_documentation_tooltip_window->set_window_type(GWindowType::Tooltip); + + m_documentation_html_view = HtmlView::construct(nullptr); + m_documentation_tooltip_window->set_main_widget(m_documentation_html_view); +} + +Editor::~Editor() +{ +} EditorWrapper& Editor::wrapper() { @@ -20,7 +46,7 @@ void Editor::focusin_event(CEvent& event) GTextEditor::focusin_event(event); } -void Editor::focusout_event(CEvent & event) +void Editor::focusout_event(CEvent& event) { wrapper().set_editor_has_focus({}, false); GTextEditor::focusout_event(event); @@ -42,3 +68,93 @@ void Editor::paint_event(GPaintEvent& event) painter.draw_rect(rect, Color::from_rgb(0x955233)); } } + +static HashMap<String, String>& man_paths() +{ + static HashMap<String, String> paths; + if (paths.is_empty()) { + // FIXME: This should also search man3, possibly other places.. + CDirIterator it("/usr/share/man/man2", CDirIterator::Flags::SkipDots); + while (it.has_next()) { + auto path = String::format("/usr/share/man/man2/%s", it.next_path().characters()); + auto title = FileSystemPath(path).title(); + paths.set(title, path); + } + } + + return paths; +} + +void Editor::show_documentation_tooltip_if_available(const String& hovered_token, const Point& screen_location) +{ + auto it = man_paths().find(hovered_token); + if (it == man_paths().end()) { + dbg() << "no man path for " << hovered_token; + m_documentation_tooltip_window->hide(); + return; + } + + dbg() << "opening " << it->value; + auto file = CFile::construct(it->value); + if (!file->open(CFile::ReadOnly)) { + dbg() << "failed to open " << it->value << " " << file->error_string(); + return; + } + + MDDocument man_document; + bool success = man_document.parse(file->read_all()); + + if (!success) { + dbg() << "failed to parse markdown"; + return; + } + + auto html_text = man_document.render_to_html(); + + auto html_document = parse_html(html_text); + if (!html_document) { + dbg() << "failed to parse HTML"; + return; + } + + // FIXME: LibHTML needs a friendlier DOM manipulation API. Something like innerHTML :^) + auto style_element = create_element(*html_document, "style"); + style_element->append_child(adopt(*new Text(*html_document, "body { background-color: #dac7b5; }"))); + + // FIXME: This const_cast should not be necessary. + auto* head_element = const_cast<HTMLHeadElement*>(html_document->head()); + ASSERT(head_element); + head_element->append_child(style_element); + + m_documentation_html_view->set_document(html_document); + m_documentation_tooltip_window->move_to(screen_location.translated(4, 4)); + m_documentation_tooltip_window->show(); +} + +void Editor::mousemove_event(GMouseEvent& event) +{ + GTextEditor::mousemove_event(event); + + if (document().spans().is_empty()) + return; + + auto text_position = text_position_at(event.position()); + if (!text_position.is_valid()) { + GApplication::the().hide_tooltip(); + return; + } + + for (auto& span : document().spans()) { + if (span.range.contains(text_position)) { + auto adjusted_range = span.range; + adjusted_range.end().set_column(adjusted_range.end().column() + 1); + auto hovered_span_text = document().text_in_range(adjusted_range); +#ifdef EDITOR_DEBUG + dbg() << "Hovering: " << adjusted_range << " \"" << hovered_span_text << "\""; +#endif + show_documentation_tooltip_if_available(hovered_span_text, event.position().translated(screen_relative_rect().location())); + return; + } + } + GApplication::the().hide_tooltip(); +} diff --git a/DevTools/HackStudio/Editor.h b/DevTools/HackStudio/Editor.h index 92858b9e69..9d4a9e93ff 100644 --- a/DevTools/HackStudio/Editor.h +++ b/DevTools/HackStudio/Editor.h @@ -3,11 +3,12 @@ #include <LibGUI/GTextEditor.h> class EditorWrapper; +class HtmlView; class Editor final : public GTextEditor { C_OBJECT(Editor) public: - virtual ~Editor() override {} + virtual ~Editor() override; Function<void()> on_focus; @@ -18,9 +19,12 @@ private: virtual void focusin_event(CEvent&) override; virtual void focusout_event(CEvent&) override; virtual void paint_event(GPaintEvent&) override; + virtual void mousemove_event(GMouseEvent&) override; - Editor(GWidget* parent) - : GTextEditor(GTextEditor::MultiLine, parent) - { - } + void show_documentation_tooltip_if_available(const String&, const Point& screen_location); + + explicit Editor(GWidget* parent); + + RefPtr<GWindow> m_documentation_tooltip_window; + RefPtr<HtmlView> m_documentation_html_view; }; diff --git a/DevTools/HackStudio/Makefile b/DevTools/HackStudio/Makefile index c92fd15e1b..8d874da8cf 100644 --- a/DevTools/HackStudio/Makefile +++ b/DevTools/HackStudio/Makefile @@ -19,7 +19,7 @@ DEFINES += -DUSERLAND all: $(APP) $(APP): $(OBJS) - $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lvt -lgui -ldraw -lthread -lcore -lc + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lvt -lhtml -lmarkdown -lgui -ldraw -lthread -lcore -lc .cpp.o: @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< |