From fd66dda1d7ec92cecbaae3c8a714725568d34751 Mon Sep 17 00:00:00 2001 From: Arne Elster Date: Wed, 10 Nov 2021 01:37:10 +0100 Subject: [PATCH] HexEditor: Stream input files instead of keeping them in memory To support editing of large files it is an advantage to not load the entire file into memory but only load whatever is needed for display at the moment. To make it work, file access is abstracted into a socalled HexDocument, of which there two: a memory based and a file based one. The former can be used for newly created documents, the latter for file based editing. Hex documents now do track changes instead of the HexEditor. HexEditor only sets new values. This frees HexEditor of some responsibility. --- .../Applications/HexEditor/CMakeLists.txt | 1 + .../Applications/HexEditor/HexDocument.cpp | 154 +++++++++++++ Userland/Applications/HexEditor/HexDocument.h | 74 +++++++ Userland/Applications/HexEditor/HexEditor.cpp | 208 ++++++++++-------- Userland/Applications/HexEditor/HexEditor.h | 15 +- .../HexEditor/HexEditorWidget.cpp | 25 +-- .../HexEditor/SearchResultsModel.h | 2 +- 7 files changed, 362 insertions(+), 117 deletions(-) create mode 100644 Userland/Applications/HexEditor/HexDocument.cpp create mode 100644 Userland/Applications/HexEditor/HexDocument.h diff --git a/Userland/Applications/HexEditor/CMakeLists.txt b/Userland/Applications/HexEditor/CMakeLists.txt index 19414e8bdb..e51ef619ec 100644 --- a/Userland/Applications/HexEditor/CMakeLists.txt +++ b/Userland/Applications/HexEditor/CMakeLists.txt @@ -11,6 +11,7 @@ compile_gml(FindDialog.gml FindDialogGML.h find_dialog_gml) set(SOURCES HexEditor.cpp HexEditorWidget.cpp + HexDocument.cpp FindDialog.cpp GoToOffsetDialog.cpp main.cpp diff --git a/Userland/Applications/HexEditor/HexDocument.cpp b/Userland/Applications/HexEditor/HexDocument.cpp new file mode 100644 index 0000000000..0b6acc1c3e --- /dev/null +++ b/Userland/Applications/HexEditor/HexDocument.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021, Arne Elster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "HexDocument.h" + +void HexDocument::set(size_t position, u8 value) +{ + m_changes.set(position, value); +} + +bool HexDocument::is_dirty() const +{ + return m_changes.size() > 0; +} + +HexDocumentMemory::HexDocumentMemory(ByteBuffer&& buffer) + : m_buffer(move(buffer)) +{ +} + +HexDocument::Cell HexDocumentMemory::get(size_t position) +{ + auto tracked_change = m_changes.get(position); + if (tracked_change.has_value()) { + return Cell { tracked_change.value(), true }; + } else { + return Cell { m_buffer[position], false }; + } +} + +size_t HexDocumentMemory::size() const +{ + return m_buffer.size(); +} + +HexDocument::Type HexDocumentMemory::type() const +{ + return Type::Memory; +} + +void HexDocumentMemory::clear_changes() +{ + m_changes.clear(); +} + +bool HexDocumentMemory::write_to_file(NonnullRefPtr file) +{ + if (!file->seek(0)) + return false; + if (!file->write(m_buffer.data(), m_buffer.size())) + return false; + for (auto& change : m_changes) { + file->seek(change.key, Core::SeekMode::SetPosition); + file->write(&change.value, 1); + } + return true; +} + +HexDocumentFile::HexDocumentFile(NonnullRefPtr file) + : m_file(file) +{ + set_file(file); +} + +void HexDocumentFile::write_to_file() +{ + for (auto& change : m_changes) { + m_file->seek(change.key, Core::SeekMode::SetPosition); + m_file->write(&change.value, 1); + } + clear_changes(); + // make sure the next get operation triggers a read + m_buffer_file_pos = m_file_size + 1; +} + +bool HexDocumentFile::write_to_file(NonnullRefPtr file) +{ + if (!file->truncate(size())) { + return false; + } + + if (!file->seek(0) || !m_file->seek(0)) { + return false; + } + + while (true) { + auto copy_buffer = m_file->read(64 * KiB); + if (copy_buffer.size() == 0) + break; + file->write(copy_buffer.data(), copy_buffer.size()); + } + + for (auto& change : m_changes) { + file->seek(change.key, Core::SeekMode::SetPosition); + file->write(&change.value, 1); + } + + return true; +} + +HexDocument::Cell HexDocumentFile::get(size_t position) +{ + auto tracked_change = m_changes.get(position); + if (tracked_change.has_value()) { + return Cell { tracked_change.value(), true }; + } + + if (position < m_buffer_file_pos || position >= m_buffer_file_pos + m_buffer.size()) { + m_file->seek(position, Core::SeekMode::SetPosition); + m_file->read(m_buffer.data(), m_buffer.size()); + m_buffer_file_pos = position; + } + return { m_buffer[position - m_buffer_file_pos], false }; +} + +size_t HexDocumentFile::size() const +{ + return m_file_size; +} + +HexDocument::Type HexDocumentFile::type() const +{ + return Type::File; +} + +void HexDocumentFile::clear_changes() +{ + m_changes.clear(); +} + +void HexDocumentFile::set_file(NonnullRefPtr file) +{ + m_file = file; + + off_t size = 0; + if (!file->seek(0, Core::SeekMode::FromEndPosition, &size)) { + m_file_size = 0; + } else { + m_file_size = size; + } + file->seek(0, Core::SeekMode::SetPosition); + + clear_changes(); + // make sure the next get operation triggers a read + m_buffer_file_pos = m_file_size + 1; +} + +NonnullRefPtr HexDocumentFile::file() const +{ + return m_file; +} diff --git a/Userland/Applications/HexEditor/HexDocument.h b/Userland/Applications/HexEditor/HexDocument.h new file mode 100644 index 0000000000..e5948f8e80 --- /dev/null +++ b/Userland/Applications/HexEditor/HexDocument.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021, Arne Elster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +class HexDocument { +public: + enum class Type { + Memory, + File + }; + + struct Cell { + u8 value; + bool modified; + }; + + virtual ~HexDocument() = default; + virtual Cell get(size_t position) = 0; + virtual void set(size_t position, u8 value); + virtual size_t size() const = 0; + virtual Type type() const = 0; + virtual bool is_dirty() const; + virtual void clear_changes() = 0; + +protected: + HashMap m_changes; +}; + +class HexDocumentMemory : public HexDocument { +public: + explicit HexDocumentMemory(ByteBuffer&& buffer); + virtual ~HexDocumentMemory() = default; + + Cell get(size_t position) override; + size_t size() const override; + Type type() const override; + void clear_changes() override; + bool write_to_file(NonnullRefPtr file); + +private: + ByteBuffer m_buffer; +}; + +class HexDocumentFile : public HexDocument { +public: + explicit HexDocumentFile(NonnullRefPtr file); + virtual ~HexDocumentFile() = default; + + HexDocumentFile(const HexDocumentFile&) = delete; + + void set_file(NonnullRefPtr file); + NonnullRefPtr file() const; + void write_to_file(); + bool write_to_file(NonnullRefPtr file); + Cell get(size_t position) override; + size_t size() const override; + Type type() const override; + void clear_changes() override; + +private: + NonnullRefPtr m_file; + size_t m_file_size; + + Array m_buffer; + size_t m_buffer_file_pos; +}; diff --git a/Userland/Applications/HexEditor/HexEditor.cpp b/Userland/Applications/HexEditor/HexEditor.cpp index 35dc37f6ec..eb47bc0614 100644 --- a/Userland/Applications/HexEditor/HexEditor.cpp +++ b/Userland/Applications/HexEditor/HexEditor.cpp @@ -28,6 +28,7 @@ HexEditor::HexEditor() : m_blink_timer(Core::Timer::construct()) + , m_document(make(ByteBuffer::create_zeroed(0).release_value())) { set_should_hide_unnecessary_scrollbars(true); set_focus_policy(GUI::FocusPolicy::StrongFocus); @@ -56,11 +57,27 @@ void HexEditor::set_readonly(bool readonly) m_readonly = readonly; } -void HexEditor::set_buffer(const ByteBuffer& buffer) +bool HexEditor::open_new_file(size_t size) { - m_buffer = buffer; - set_content_length(buffer.size()); - m_tracked_changes.clear(); + auto maybe_buffer = ByteBuffer::create_zeroed(size); + if (!maybe_buffer.has_value()) { + return false; + } + + m_document = make(maybe_buffer.release_value()); + set_content_length(m_document->size()); + m_position = 0; + m_cursor_at_low_nibble = false; + update(); + update_status(); + + return true; +} + +void HexEditor::open_file(NonnullRefPtr file) +{ + m_document = make(file); + set_content_length(m_document->size()); m_position = 0; m_cursor_at_low_nibble = false; update(); @@ -72,9 +89,8 @@ void HexEditor::fill_selection(u8 fill_byte) if (!has_selection()) return; - for (size_t i = m_selection_start; i < m_selection_end; i++) { - m_tracked_changes.set(i, m_buffer.data()[i]); - m_buffer.data()[i] = fill_byte; + for (size_t i = m_selection_start; i <= m_selection_end; i++) { + m_document->set(i, fill_byte); } update(); @@ -83,7 +99,7 @@ void HexEditor::fill_selection(u8 fill_byte) void HexEditor::set_position(size_t position) { - if (position > m_buffer.size()) + if (position > m_document->size()) return; m_position = position; @@ -93,42 +109,37 @@ void HexEditor::set_position(size_t position) update_status(); } -bool HexEditor::write_to_file(const String& path) +bool HexEditor::save_as(int fd) { - if (m_buffer.is_empty()) - return true; - - int fd = open(path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) { - perror("open"); + auto new_file = Core::File::construct(); + if (!new_file->open(fd, Core::OpenMode::ReadWrite, Core::File::ShouldCloseFileDescriptor::Yes)) { return false; } - return write_to_file(fd); + if (m_document->type() == HexDocument::Type::File) { + HexDocumentFile* fileDoc = static_cast(m_document.ptr()); + if (!fileDoc->write_to_file(new_file)) + return false; + fileDoc->set_file(new_file); + } else { + HexDocumentMemory* memDoc = static_cast(m_document.ptr()); + if (!memDoc->write_to_file(new_file)) + return false; + m_document = make(new_file); + } + + update(); + + return true; } -bool HexEditor::write_to_file(int fd) +bool HexEditor::save() { - ScopeGuard fd_guard = [fd] { close(fd); }; - - int rc = ftruncate(fd, m_buffer.size()); - if (rc < 0) { - perror("ftruncate"); + if (m_document->type() != HexDocument::Type::File) { return false; } - ssize_t nwritten = write(fd, m_buffer.data(), m_buffer.size()); - if (nwritten < 0) { - perror("write"); - close(fd); - return false; - } - - if (static_cast(nwritten) == m_buffer.size()) { - m_tracked_changes.clear(); - update(); - } - + static_cast(m_document.ptr())->write_to_file(); return true; } @@ -145,8 +156,8 @@ bool HexEditor::copy_selected_hex_to_clipboard() return false; StringBuilder output_string_builder; - for (size_t i = m_selection_start; i < m_selection_end; i++) - output_string_builder.appendff("{:02X} ", m_buffer.data()[i]); + for (size_t i = m_selection_start; i <= m_selection_end; i++) + output_string_builder.appendff("{:02X} ", m_document->get(i).value); GUI::Clipboard::the().set_plain_text(output_string_builder.to_string()); return true; @@ -158,8 +169,8 @@ bool HexEditor::copy_selected_text_to_clipboard() return false; StringBuilder output_string_builder; - for (size_t i = m_selection_start; i < m_selection_end; i++) - output_string_builder.append(isprint(m_buffer.data()[i]) ? m_buffer[i] : '.'); + for (size_t i = m_selection_start; i <= m_selection_end; i++) + output_string_builder.append(isprint(m_document->get(i).value) ? m_document->get(i).value : '.'); GUI::Clipboard::the().set_plain_text(output_string_builder.to_string()); return true; @@ -173,8 +184,8 @@ bool HexEditor::copy_selected_hex_to_clipboard_as_c_code() StringBuilder output_string_builder; output_string_builder.appendff("unsigned char raw_data[{}] = {{\n", (m_selection_end - m_selection_start) + 1); output_string_builder.append(" "); - for (size_t i = m_selection_start, j = 1; i < m_selection_end; i++, j++) { - output_string_builder.appendff("{:#02X}", m_buffer.data()[i]); + for (size_t i = m_selection_start, j = 1; i <= m_selection_end; i++, j++) { + output_string_builder.appendff("{:#02X}", m_document->get(i).value); if (i != m_selection_end) output_string_builder.append(", "); if ((j % 12) == 0) { @@ -234,7 +245,7 @@ void HexEditor::mousedown_event(GUI::MouseEvent& event) auto byte_y = (absolute_y - hex_start_y) / line_height(); auto offset = (byte_y * m_bytes_per_row) + byte_x; - if (offset >= m_buffer.size()) + if (offset >= m_document->size()) return; dbgln_if(HEX_DEBUG, "HexEditor::mousedown_event(hex): offset={}", offset); @@ -257,7 +268,7 @@ void HexEditor::mousedown_event(GUI::MouseEvent& event) auto byte_y = (absolute_y - text_start_y) / line_height(); auto offset = (byte_y * m_bytes_per_row) + byte_x; - if (offset >= m_buffer.size()) + if (offset >= m_document->size()) return; dbgln_if(HEX_DEBUG, "HexEditor::mousedown_event(text): offset={}", offset); @@ -306,7 +317,7 @@ void HexEditor::mousemove_event(GUI::MouseEvent& event) auto byte_y = (absolute_y - hex_start_y) / line_height(); auto offset = (byte_y * m_bytes_per_row) + byte_x; - if (offset > m_buffer.size()) + if (offset > m_document->size()) return; m_selection_end = offset; @@ -321,8 +332,7 @@ void HexEditor::mousemove_event(GUI::MouseEvent& event) auto byte_x = (absolute_x - text_start_x) / character_width(); auto byte_y = (absolute_y - text_start_y) / line_height(); auto offset = (byte_y * m_bytes_per_row) + byte_x; - - if (offset > m_buffer.size()) + if (offset > m_document->size()) return; m_selection_end = offset; @@ -383,7 +393,7 @@ void HexEditor::keydown_event(GUI::KeyEvent& event) } if (event.key() == KeyCode::Key_Down) { - if (m_position + bytes_per_row() < m_buffer.size()) { + if (m_position + bytes_per_row() < m_document->size()) { m_position += bytes_per_row(); m_selection_start = m_selection_end = m_position; m_cursor_at_low_nibble = false; @@ -409,7 +419,7 @@ void HexEditor::keydown_event(GUI::KeyEvent& event) } if (event.key() == KeyCode::Key_Right) { - if (m_position + 1 < m_buffer.size()) { + if (m_position + 1 < m_document->size()) { m_position++; m_selection_start = m_selection_end = m_position; m_cursor_at_low_nibble = false; @@ -446,12 +456,10 @@ void HexEditor::keydown_event(GUI::KeyEvent& event) void HexEditor::hex_mode_keydown_event(GUI::KeyEvent& event) { if ((event.key() >= KeyCode::Key_0 && event.key() <= KeyCode::Key_9) || (event.key() >= KeyCode::Key_A && event.key() <= KeyCode::Key_F)) { - if (m_buffer.is_empty()) - return; - if (m_position == m_buffer.size()) + if (m_document->size() == 0) return; - VERIFY(m_position <= m_buffer.size()); + VERIFY(m_position <= m_document->size()); // yes, this is terrible... but it works. auto value = (event.key() >= KeyCode::Key_0 && event.key() <= KeyCode::Key_9) @@ -459,12 +467,13 @@ void HexEditor::hex_mode_keydown_event(GUI::KeyEvent& event) : (event.key() - KeyCode::Key_A) + 0xA; if (!m_cursor_at_low_nibble) { - m_tracked_changes.set(m_position, m_buffer.data()[m_position]); - m_buffer.data()[m_position] = value << 4 | (m_buffer.data()[m_position] & 0xF); // shift new value left 4 bits, OR with existing last 4 bits + u8 existing_change = m_document->get(m_position).value; + existing_change = value << 4 | (existing_change & 0xF); // shift new value left 4 bits, OR with existing last 4 bits + m_document->set(m_position, existing_change); m_cursor_at_low_nibble = true; } else { - m_buffer.data()[m_position] = (m_buffer.data()[m_position] & 0xF0) | value; // save the first 4 bits, OR the new value in the last 4 - if (m_position + 1 < m_buffer.size()) + m_document->set(m_position, (m_document->get(m_position).value & 0xF0) | value); // save the first 4 bits, OR the new value in the last 4 + if (m_position + 1 < m_document->size()) m_position++; m_cursor_at_low_nibble = false; } @@ -478,16 +487,15 @@ void HexEditor::hex_mode_keydown_event(GUI::KeyEvent& event) void HexEditor::text_mode_keydown_event(GUI::KeyEvent& event) { - if (m_buffer.is_empty()) + if (m_document->size() == 0) return; - VERIFY(m_position < m_buffer.size()); + VERIFY(m_position < m_document->size()); if (event.code_point() == 0) // This is a control key return; - m_tracked_changes.set(m_position, m_buffer.data()[m_position]); - m_buffer.data()[m_position] = event.code_point(); - if (m_position + 1 < m_buffer.size()) + m_document->set(m_position, event.code_point()); + if (m_position + 1 < m_document->size()) m_position++; m_cursor_at_low_nibble = false; @@ -518,7 +526,7 @@ void HexEditor::paint_event(GUI::PaintEvent& event) painter.add_clip_rect(event.rect()); painter.fill_rect(event.rect(), palette().color(background_role())); - if (m_buffer.is_empty()) + if (m_document->size() == 0) return; painter.translate(frame_thickness(), frame_thickness()); @@ -564,10 +572,10 @@ void HexEditor::paint_event(GUI::PaintEvent& event) for (size_t i = min_row; i < max_row; i++) { for (size_t j = 0; j < bytes_per_row(); j++) { auto byte_position = (i * bytes_per_row()) + j; - if (byte_position >= m_buffer.size()) + if (byte_position >= m_document->size()) return; - const bool edited_flag = m_tracked_changes.contains(byte_position); + const bool edited_flag = m_document->get(byte_position).modified; const bool selection_inbetween_start_end = byte_position >= m_selection_start && byte_position < m_selection_end; const bool selection_inbetween_end_start = byte_position >= m_selection_end && byte_position < m_selection_start; @@ -592,7 +600,8 @@ void HexEditor::paint_event(GUI::PaintEvent& event) } painter.fill_rect(hex_display_rect, background_color); - auto line = String::formatted("{:02X}", m_buffer[byte_position]); + const u8 cell_value = m_document->get(byte_position).value; + auto line = String::formatted("{:02X}", cell_value); painter.draw_text(hex_display_rect, line, Gfx::TextAlignment::TopLeft, text_color); if (m_edit_mode == EditMode::Hex) { @@ -626,7 +635,7 @@ void HexEditor::paint_event(GUI::PaintEvent& event) } painter.fill_rect(text_display_rect, background_color); - painter.draw_text(text_display_rect, String::formatted("{:c}", isprint(m_buffer[byte_position]) ? m_buffer[byte_position] : '.'), Gfx::TextAlignment::TopLeft, text_color); + painter.draw_text(text_display_rect, String::formatted("{:c}", isprint(cell_value) ? cell_value : '.'), Gfx::TextAlignment::TopLeft, text_color); if (m_edit_mode == EditMode::Text) { if (byte_position == m_position && m_cursor_blink_active) { @@ -642,7 +651,7 @@ void HexEditor::paint_event(GUI::PaintEvent& event) void HexEditor::select_all() { - highlight(0, m_buffer.size() - 1); + highlight(0, m_document->size() - 1); set_position(0); } @@ -664,38 +673,50 @@ Optional HexEditor::find_and_highlight(ByteBuffer& needle, size_t start) Optional HexEditor::find(ByteBuffer& needle, size_t start) { - if (m_buffer.is_empty()) + if (m_document->size() == 0) return {}; - auto raw_offset = memmem(m_buffer.data() + start, m_buffer.size() - start, needle.data(), needle.size()); - if (raw_offset == NULL) - return {}; + for (size_t i = start; i < m_document->size(); i++) { + if (m_document->get(i).value == *needle.data()) { + bool found = true; + for (size_t j = 1; j < needle.size(); j++) { + if (m_document->get(i + j).value != needle.data()[j]) { + found = false; + break; + } + } + if (found) { + auto end_of_match = i + needle.size(); + return end_of_match; + } + } + } - int relative_offset = static_cast(raw_offset) - m_buffer.data(); - dbgln("find: start={} raw_offset={} relative_offset={}", start, raw_offset, relative_offset); - - auto end_of_match = relative_offset + needle.size(); - - return end_of_match; + return {}; } Vector HexEditor::find_all(ByteBuffer& needle, size_t start) { - if (m_buffer.is_empty()) + if (m_document->size() == 0 || needle.size() == 0) return {}; Vector matches; size_t i = start; - while (i < m_buffer.size()) { - auto raw_offset = memmem(m_buffer.data() + i, m_buffer.size() - i, needle.data(), needle.size()); - if (raw_offset == NULL) - break; - - int relative_offset = static_cast(raw_offset) - m_buffer.data(); - dbgln("find_all: needle={} start={} raw_offset={} relative_offset={}", needle.data(), i, raw_offset, relative_offset); - matches.append({ relative_offset, String::formatted("{}", StringView { needle }.to_string().characters()) }); - i = relative_offset + needle.size(); + for (; i < m_document->size(); i++) { + if (m_document->get(i).value == *needle.data()) { + bool found = true; + for (size_t j = 1; j < needle.size(); j++) { + if (m_document->get(i + j).value != needle.data()[j]) { + found = false; + break; + } + } + if (found) { + matches.append({ i, String::formatted("{}", StringView { needle }.to_string().characters()) }); + i += needle.size() - 1; + } + } } if (matches.is_empty()) @@ -709,18 +730,21 @@ Vector HexEditor::find_all(ByteBuffer& needle, size_t start) Vector HexEditor::find_all_strings(size_t min_length) { - if (m_buffer.is_empty()) + if (m_document->size() == 0) return {}; Vector matches; - int offset = -1; + bool found_string = false; + size_t offset = 0; StringBuilder builder; - for (size_t i = 0; i < m_buffer.size(); i++) { - char c = m_buffer.bytes().at(i); + for (size_t i = 0; i < m_document->size(); i++) { + char c = m_document->get(i).value; if (isprint(c)) { - if (offset == -1) + if (!found_string) { offset = i; + found_string = true; + } builder.append(c); } else { if (builder.length() >= min_length) { @@ -728,7 +752,7 @@ Vector HexEditor::find_all_strings(size_t min_length) matches.append({ offset, builder.to_string() }); } builder.clear(); - offset = -1; + found_string = false; } } diff --git a/Userland/Applications/HexEditor/HexEditor.h b/Userland/Applications/HexEditor/HexEditor.h index 5aeab329c8..4b17233274 100644 --- a/Userland/Applications/HexEditor/HexEditor.h +++ b/Userland/Applications/HexEditor/HexEditor.h @@ -7,6 +7,7 @@ #pragma once +#include "HexDocument.h" #include "SearchResultsModel.h" #include #include @@ -32,14 +33,15 @@ public: bool is_readonly() const { return m_readonly; } void set_readonly(bool); - size_t buffer_size() const { return m_buffer.size(); } - void set_buffer(const ByteBuffer&); + size_t buffer_size() const { return m_document->size(); } + bool open_new_file(size_t size); + void open_file(NonnullRefPtr file); void fill_selection(u8 fill_byte); - bool write_to_file(const String& path); - bool write_to_file(int fd); + bool save_as(int fd); + bool save(); void select_all(); - bool has_selection() const { return !((m_selection_end < m_selection_start) || m_buffer.is_empty()); } + bool has_selection() const { return !((m_selection_end < m_selection_start) || m_document->size()); } size_t selection_size(); size_t selection_start_offset() const { return m_selection_start; } bool copy_selected_text_to_clipboard(); @@ -72,16 +74,15 @@ private: size_t m_line_spacing { 4 }; size_t m_content_length { 0 }; size_t m_bytes_per_row { 16 }; - ByteBuffer m_buffer; bool m_in_drag_select { false }; size_t m_selection_start { 0 }; size_t m_selection_end { 0 }; - HashMap m_tracked_changes; size_t m_position { 0 }; bool m_cursor_at_low_nibble { false }; EditMode m_edit_mode { Hex }; NonnullRefPtr m_blink_timer; bool m_cursor_blink_active { false }; + NonnullOwnPtr m_document; static constexpr int m_address_bar_width = 90; static constexpr int m_padding = 5; diff --git a/Userland/Applications/HexEditor/HexEditorWidget.cpp b/Userland/Applications/HexEditor/HexEditorWidget.cpp index 5a37d385b3..b40cc47700 100644 --- a/Userland/Applications/HexEditor/HexEditorWidget.cpp +++ b/Userland/Applications/HexEditor/HexEditorWidget.cpp @@ -75,12 +75,10 @@ HexEditorWidget::HexEditorWidget() auto file_size = value.to_int(); if (file_size.has_value() && file_size.value() > 0) { m_document_dirty = false; - auto buffer_result = ByteBuffer::create_zeroed(file_size.value()); - if (!buffer_result.has_value()) { + if (!m_editor->open_new_file(file_size.value())) { GUI::MessageBox::show(window(), "Entered file size is too large.", "Error", GUI::MessageBox::Type::Error); return; } - m_editor->set_buffer(buffer_result.release_value()); set_path({}); update_title(); } else { @@ -90,7 +88,7 @@ HexEditorWidget::HexEditorWidget() }); m_open_action = GUI::CommonActions::make_open_action([this](auto&) { - auto response = FileSystemAccessClient::Client::the().open_file(window()->window_id()); + auto response = FileSystemAccessClient::Client::the().open_file(window()->window_id(), {}, Core::StandardPaths::home_directory(), Core::OpenMode::ReadWrite); if (response.error != 0) { if (response.error != -1) @@ -113,25 +111,18 @@ HexEditorWidget::HexEditorWidget() if (m_path.is_empty()) return m_save_as_action->activate(); - auto response = FileSystemAccessClient::Client::the().request_file(window()->window_id(), m_path, Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); - - if (response.error != 0) { - if (response.error != -1) - GUI::MessageBox::show_error(window(), String::formatted("Unable to save file: {}", strerror(response.error))); - return; - } - - if (!m_editor->write_to_file(*response.fd)) { + if (!m_editor->save()) { GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error); } else { m_document_dirty = false; + m_editor->update(); update_title(); } return; }); m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) { - auto response = FileSystemAccessClient::Client::the().save_file(window()->window_id(), m_name, m_extension); + auto response = FileSystemAccessClient::Client::the().save_file(window()->window_id(), m_name, m_extension, Core::OpenMode::ReadWrite | Core::OpenMode::Truncate); if (response.error != 0) { if (response.error != -1) @@ -139,7 +130,7 @@ HexEditorWidget::HexEditorWidget() return; } - if (!m_editor->write_to_file(*response.fd)) { + if (!m_editor->save_as(*response.fd)) { GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error); return; } @@ -357,7 +348,7 @@ void HexEditorWidget::open_file(int fd, String const& path) VERIFY(path.starts_with("/"sv)); auto file = Core::File::construct(); - if (!file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) { + if (!file->open(fd, Core::OpenMode::ReadWrite, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) { GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", path, strerror(errno)), "Error", GUI::MessageBox::Type::Error); return; } @@ -373,7 +364,7 @@ void HexEditorWidget::open_file(int fd, String const& path) } m_document_dirty = false; - m_editor->set_buffer(file->read_all()); // FIXME: On really huge files, this is never going to work. Should really create a framework to fetch data from the file on-demand. + m_editor->open_file(file); set_path(path); } diff --git a/Userland/Applications/HexEditor/SearchResultsModel.h b/Userland/Applications/HexEditor/SearchResultsModel.h index edf9ec695a..dff620df99 100644 --- a/Userland/Applications/HexEditor/SearchResultsModel.h +++ b/Userland/Applications/HexEditor/SearchResultsModel.h @@ -14,7 +14,7 @@ #include struct Match { - int offset; + u64 offset; String value; };