HackStudio: Add auto-complete capability to the Editor

This commit is contained in:
Itamar 2020-09-20 20:58:46 +03:00 committed by Andreas Kling
parent 7d6e6eb268
commit b7bd2ed9d2
Notes: sideshowbarker 2024-07-19 02:17:43 +09:00
3 changed files with 111 additions and 2 deletions

View file

@ -25,6 +25,7 @@
*/
#include "Editor.h"
#include "CppAutoComplete.h"
#include "EditorWrapper.h"
#include <AK/ByteBuffer.h>
#include <AK/LexicalPath.h>
@ -53,6 +54,8 @@ Editor::Editor()
m_documentation_tooltip_window->set_rect(0, 0, 500, 400);
m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip);
m_documentation_page_view = m_documentation_tooltip_window->set_main_widget<Web::InProcessWebView>();
m_autocomplete_box = make<AutoCompleteBox>(make_weak_ptr());
}
Editor::~Editor()
@ -307,9 +310,49 @@ void Editor::mousedown_event(GUI::MouseEvent& event)
void Editor::keydown_event(GUI::KeyEvent& event)
{
if (m_autocomplete_in_focus) {
if (event.key() == Key_Escape) {
m_autocomplete_in_focus = false;
m_autocomplete_box->close();
return;
}
if (event.key() == Key_Down) {
m_autocomplete_box->next_suggestion();
return;
}
if (event.key() == Key_Up) {
m_autocomplete_box->previous_suggestion();
return;
}
if (event.key() == Key_Return || event.key() == Key_Tab) {
m_autocomplete_box->apply_suggestion();
close_autocomplete();
return;
}
}
auto autocomplete_action = [this]() {
auto data = get_autocomplete_request_data();
if (data.has_value()) {
update_autocomplete(data.value());
if (m_autocomplete_in_focus)
show_autocomplete(data.value());
} else {
close_autocomplete();
}
};
if (event.key() == Key_Control)
m_holding_ctrl = true;
if (m_holding_ctrl && event.key() == Key_Space) {
autocomplete_action();
}
GUI::TextEditor::keydown_event(event);
if (m_autocomplete_in_focus) {
autocomplete_action();
}
}
void Editor::keyup_event(GUI::KeyEvent& event)
@ -421,4 +464,55 @@ void Editor::set_document(GUI::TextDocument& doc)
GUI::TextEditor::set_document(doc);
}
Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data()
{
auto highlighter = wrapper().editor().syntax_highlighter();
if (!highlighter)
return {};
auto& spans = document().spans();
for (size_t span_index = 2; span_index < spans.size(); ++span_index) {
auto& span = spans[span_index];
if (!span.range.contains(cursor())) {
continue;
}
if (highlighter->is_identifier(spans[span_index - 1].data)) {
auto completion_span = spans[span_index - 1];
auto adjusted_range = completion_span.range;
auto end_line_length = document().line(completion_span.range.end().line()).length();
adjusted_range.end().set_column(min(end_line_length, adjusted_range.end().column() + 1));
auto text_in_span = document().text_in_range(adjusted_range);
return AutoCompleteRequestData { completion_span.range.end(), text_in_span };
}
}
return {};
}
void Editor::update_autocomplete(const AutoCompleteRequestData& data)
{
// TODO: Move this part to a language server component :)
auto suggestions = CppAutoComplete::get_suggestions(text(), data.position);
if (suggestions.is_empty()) {
close_autocomplete();
return;
}
m_autocomplete_box->update_suggestions(data.partial_input, move(suggestions));
m_autocomplete_in_focus = true;
}
void Editor::show_autocomplete(const AutoCompleteRequestData& data)
{
auto suggestion_box_location = content_rect_for_position(data.position).bottom_right().translated(screen_relative_rect().top_left().translated(ruler_width(), 0).translated(10, 5));
m_autocomplete_box->show(suggestion_box_location);
}
void Editor::close_autocomplete()
{
m_autocomplete_box->close();
m_autocomplete_in_focus = false;
}
}

View file

@ -26,9 +26,11 @@
#pragma once
#include "AutoCompleteBox.h"
#include "CodeDocument.h"
#include "Debugger/BreakpointCallback.h"
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <LibGUI/TextEditor.h>
#include <LibWeb/Forward.h>
@ -78,15 +80,28 @@ private:
static const Gfx::Bitmap& breakpoint_icon_bitmap();
static const Gfx::Bitmap& current_position_icon_bitmap();
struct AutoCompleteRequestData {
GUI::TextPosition position;
String partial_input;
};
Optional<AutoCompleteRequestData> get_autocomplete_request_data();
void update_autocomplete(const AutoCompleteRequestData&);
void show_autocomplete(const AutoCompleteRequestData&);
void close_autocomplete();
explicit Editor();
RefPtr<GUI::Window> m_documentation_tooltip_window;
OwnPtr<AutoCompleteBox> m_autocomplete_box;
RefPtr<Web::InProcessWebView> m_documentation_page_view;
String m_last_parsed_token;
GUI::TextPosition m_previous_text_position { 0, 0 };
bool m_hovering_editor { false };
bool m_hovering_link { false };
bool m_holding_ctrl { false };
bool m_autocomplete_in_focus { false };
};
}

View file

@ -186,6 +186,8 @@ protected:
TextPosition text_position_at(const Gfx::IntPoint&) const;
bool ruler_visible() const { return m_ruler_visible; }
Gfx::IntRect content_rect_for_position(const TextPosition&) const;
int ruler_width() const;
private:
friend class TextDocumentLine;
@ -231,7 +233,6 @@ private:
Gfx::IntRect line_content_rect(size_t item_index) const;
Gfx::IntRect line_widget_rect(size_t line_index) const;
Gfx::IntRect cursor_content_rect() const;
Gfx::IntRect content_rect_for_position(const TextPosition&) const;
void update_cursor();
const NonnullOwnPtrVector<TextDocumentLine>& lines() const { return document().lines(); }
NonnullOwnPtrVector<TextDocumentLine>& lines() { return document().lines(); }
@ -239,7 +240,6 @@ private:
const TextDocumentLine& line(size_t index) const { return document().line(index); }
TextDocumentLine& current_line() { return line(m_cursor.line()); }
const TextDocumentLine& current_line() const { return line(m_cursor.line()); }
int ruler_width() const;
void toggle_selection_if_needed_for_event(const KeyEvent&);
void delete_selection();
void did_update_selection();