HackStudio: Use CodeDocument instead of TextDocument

This commit adds a new GUI widget type, called CodeDocument, which
is a TextDocument that can additionaly store data related to the
debugger.

This fixes various bugs and crashes that occured when we switched
between files in debug mode, because we previously held stale breakpoint
data for the previous file in the Editor object.
We now keep this data at the "document" level rather than the Editor
level, which fixes things.
This commit is contained in:
Itamar 2020-08-15 10:58:31 +03:00 committed by Andreas Kling
parent e793cc3d13
commit 627f258c97
Notes: sideshowbarker 2024-07-19 03:36:54 +09:00
9 changed files with 137 additions and 22 deletions

View file

@ -18,6 +18,7 @@ set(SOURCES
Tool.cpp
WidgetTool.cpp
WidgetTreeModel.cpp
CodeDocument.cpp
)
serenity_bin(HackStudio)

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "CodeDocument.h"
NonnullRefPtr<CodeDocument> CodeDocument::create(Client* client)
{
return adopt(*new CodeDocument(client));
}
CodeDocument::CodeDocument(Client* client)
: TextDocument(client)
{
}
CodeDocument::~CodeDocument()
{
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/TextDocument.h>
class CodeDocument final : public GUI::TextDocument {
public:
virtual ~CodeDocument() override;
static NonnullRefPtr<CodeDocument> create(Client* client = nullptr);
const Vector<size_t>& breakpoint_lines() const { return m_breakpoint_lines; }
Vector<size_t>& breakpoint_lines() { return m_breakpoint_lines; }
Optional<size_t> execution_position() const { return m_execution_position; }
void set_execution_position(size_t line) { m_execution_position = line; }
void clear_execution_position() { m_execution_position.clear(); }
virtual bool is_code_document() const override final { return true; }
private:
explicit CodeDocument(Client* client);
Vector<size_t> m_breakpoint_lines;
Optional<size_t> m_execution_position;
};

View file

@ -38,8 +38,8 @@
#include <LibGUI/Window.h>
#include <LibMarkdown/Document.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/HTML/HTMLHeadElement.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/HTML/HTMLHeadElement.h>
#include <LibWeb/PageView.h>
// #define EDITOR_DEBUG
@ -109,16 +109,16 @@ void Editor::paint_event(GUI::PaintEvent& event)
if (ruler_visible()) {
size_t first_visible_line = text_position_at(event.rect().top_left()).line();
size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
for (size_t line : m_breakpoint_lines) {
for (size_t line : breakpoint_lines()) {
if (line < first_visible_line || line > last_visible_line) {
continue;
}
const auto& icon = breakpoint_icon_bitmap();
painter.blit(breakpoint_icon_rect(line).center(), icon, icon.rect());
}
if (m_execution_position.has_value()) {
if (execution_position().has_value()) {
const auto& icon = current_position_icon_bitmap();
painter.blit(breakpoint_icon_rect(m_execution_position.value()).center(), icon, icon.rect());
painter.blit(breakpoint_icon_rect(execution_position().value()).center(), icon, icon.rect());
}
}
}
@ -261,11 +261,11 @@ void Editor::mousedown_event(GUI::MouseEvent& event)
auto text_position = text_position_at(event.position());
auto ruler_line_rect = ruler_content_rect(text_position.line());
if (event.button() == GUI::MouseButton::Left && event.position().x() < ruler_line_rect.width()) {
if (!m_breakpoint_lines.contains_slow(text_position.line())) {
m_breakpoint_lines.append(text_position.line());
if (!breakpoint_lines().contains_slow(text_position.line())) {
breakpoint_lines().append(text_position.line());
on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Added);
} else {
m_breakpoint_lines.remove_first_matching([&](size_t line) { return line == text_position.line(); });
breakpoint_lines().remove_first_matching([&](size_t line) { return line == text_position.line(); });
on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Removed);
}
}
@ -373,18 +373,18 @@ void Editor::navigate_to_include_if_available(String path)
void Editor::set_execution_position(size_t line_number)
{
m_execution_position = line_number;
code_document().set_execution_position(line_number);
scroll_position_into_view({ line_number, 0 });
update(breakpoint_icon_rect(line_number));
}
void Editor::clear_execution_position()
{
if (!m_execution_position.has_value()) {
if (!execution_position().has_value()) {
return;
}
size_t previous_position = m_execution_position.value();
m_execution_position = {};
size_t previous_position = execution_position().value();
code_document().clear_execution_position();
update(breakpoint_icon_rect(previous_position));
}
@ -399,3 +399,21 @@ const Gfx::Bitmap& Editor::current_position_icon_bitmap()
static auto bitmap = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png");
return *bitmap;
}
const CodeDocument& Editor::code_document() const
{
const auto& doc = document();
ASSERT(doc.is_code_document());
return static_cast<const CodeDocument&>(doc);
}
CodeDocument& Editor::code_document()
{
return const_cast<CodeDocument&>(static_cast<const Editor&>(*this).code_document());
}
void Editor::set_document(GUI::TextDocument& doc)
{
ASSERT(doc.is_code_document());
GUI::TextEditor::set_document(doc);
}

View file

@ -26,6 +26,7 @@
#pragma once
#include "CodeDocument.h"
#include "Debugger/BreakpointCallback.h"
#include <AK/Optional.h>
#include <LibGUI/TextEditor.h>
@ -44,12 +45,19 @@ public:
EditorWrapper& wrapper();
const EditorWrapper& wrapper() const;
const Vector<size_t>& breakpoint_lines() const { return m_breakpoint_lines; }
const Vector<size_t>& breakpoint_lines() const { return code_document().breakpoint_lines(); }
Vector<size_t>& breakpoint_lines() { return code_document().breakpoint_lines(); }
Optional<size_t> execution_position() const { return code_document().execution_position(); }
void set_execution_position(size_t line_number);
void clear_execution_position();
BreakpointChangeCallback on_breakpoint_change;
const CodeDocument& code_document() const;
CodeDocument& code_document();
virtual void set_document(GUI::TextDocument&) override;
private:
virtual void focusin_event(GUI::FocusEvent&) override;
virtual void focusout_event(GUI::FocusEvent&) override;
@ -77,7 +85,4 @@ private:
bool m_hovering_editor { false };
bool m_hovering_link { false };
bool m_holding_ctrl { false };
Vector<size_t> m_breakpoint_lines;
Optional<size_t> m_execution_position;
};

View file

@ -36,7 +36,7 @@ ProjectFile::ProjectFile(const String& name)
const GUI::TextDocument& ProjectFile::document() const
{
if (!m_document) {
m_document = GUI::TextDocument::create(nullptr);
m_document = CodeDocument::create(nullptr);
auto file = Core::File::construct(m_name);
if (!file->open(Core::File::ReadOnly)) {
ASSERT_NOT_REACHED();

View file

@ -26,11 +26,11 @@
#pragma once
#include "CodeDocument.h"
#include <AK/ByteBuffer.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
#include <LibGUI/TextDocument.h>
class ProjectFile : public RefCounted<ProjectFile> {
public:
@ -47,5 +47,5 @@ private:
explicit ProjectFile(const String& name);
String m_name;
mutable RefPtr<GUI::TextDocument> m_document;
mutable RefPtr<CodeDocument> m_document;
};

View file

@ -75,7 +75,7 @@ public:
};
static NonnullRefPtr<TextDocument> create(Client* client = nullptr);
~TextDocument();
virtual ~TextDocument();
size_t line_count() const { return m_lines.size(); }
const TextDocumentLine& line(size_t line_index) const { return m_lines[line_index]; }
@ -137,9 +137,12 @@ public:
TextPosition insert_at(const TextPosition&, const StringView&, const Client* = nullptr);
void remove(const TextRange&);
private:
virtual bool is_code_document() const { return false; }
protected:
explicit TextDocument(Client* client);
private:
void update_undo_timer();
NonnullOwnPtrVector<TextDocumentLine> m_lines;

View file

@ -58,7 +58,7 @@ public:
const TextDocument& document() const { return *m_document; }
TextDocument& document() { return *m_document; }
void set_document(TextDocument&);
virtual void set_document(TextDocument&);
bool has_visible_list() const { return m_has_visible_list; }
void set_has_visible_list(bool);
@ -175,7 +175,7 @@ protected:
virtual void context_menu_event(ContextMenuEvent&) override;
virtual void resize_event(ResizeEvent&) override;
virtual void theme_change_event(ThemeChangeEvent&) override;
virtual void cursor_did_change() { }
virtual void cursor_did_change() {}
Gfx::IntRect ruler_content_rect(size_t line) const;
TextPosition text_position_at(const Gfx::IntPoint&) const;