diff options
author | Arne Elster <arne@elster.li> | 2021-11-10 01:37:10 +0100 |
---|---|---|
committer | Brian Gianforcaro <b.gianfo@gmail.com> | 2021-12-23 23:25:47 -0800 |
commit | fd66dda1d7ec92cecbaae3c8a714725568d34751 (patch) | |
tree | e25a5cf78be7e8e34695771da3c14db53063693d /Userland | |
parent | 60c3ad9ae845f0582f70901a965cee41d2a774ea (diff) | |
download | serenity-fd66dda1d7ec92cecbaae3c8a714725568d34751.zip |
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.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Applications/HexEditor/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexDocument.cpp | 154 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexDocument.h | 74 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditor.cpp | 208 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditor.h | 15 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/HexEditorWidget.cpp | 25 | ||||
-rw-r--r-- | Userland/Applications/HexEditor/SearchResultsModel.h | 2 |
7 files changed, 362 insertions, 117 deletions
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 <arne@elster.li> + * + * 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<Core::File> 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<Core::File> 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<Core::File> 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<Core::File> 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<Core::File> 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 <arne@elster.li> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/StringView.h> +#include <AK/Types.h> +#include <LibCore/File.h> + +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<size_t, u8> 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<Core::File> file); + +private: + ByteBuffer m_buffer; +}; + +class HexDocumentFile : public HexDocument { +public: + explicit HexDocumentFile(NonnullRefPtr<Core::File> file); + virtual ~HexDocumentFile() = default; + + HexDocumentFile(const HexDocumentFile&) = delete; + + void set_file(NonnullRefPtr<Core::File> file); + NonnullRefPtr<Core::File> file() const; + void write_to_file(); + bool write_to_file(NonnullRefPtr<Core::File> file); + Cell get(size_t position) override; + size_t size() const override; + Type type() const override; + void clear_changes() override; + +private: + NonnullRefPtr<Core::File> m_file; + size_t m_file_size; + + Array<u8, 2048> 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<HexDocumentMemory>(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<HexDocumentMemory>(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<Core::File> file) +{ + m_document = make<HexDocumentFile>(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<HexDocumentFile*>(m_document.ptr()); + if (!fileDoc->write_to_file(new_file)) + return false; + fileDoc->set_file(new_file); + } else { + HexDocumentMemory* memDoc = static_cast<HexDocumentMemory*>(m_document.ptr()); + if (!memDoc->write_to_file(new_file)) + return false; + m_document = make<HexDocumentFile>(new_file); + } -bool HexEditor::write_to_file(int fd) -{ - ScopeGuard fd_guard = [fd] { close(fd); }; + update(); - int rc = ftruncate(fd, m_buffer.size()); - if (rc < 0) { - perror("ftruncate"); - return false; - } + return true; +} - ssize_t nwritten = write(fd, m_buffer.data(), m_buffer.size()); - if (nwritten < 0) { - perror("write"); - close(fd); +bool HexEditor::save() +{ + if (m_document->type() != HexDocument::Type::File) { return false; } - if (static_cast<size_t>(nwritten) == m_buffer.size()) { - m_tracked_changes.clear(); - update(); - } - + static_cast<HexDocumentFile*>(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<size_t> HexEditor::find_and_highlight(ByteBuffer& needle, size_t start) Optional<size_t> HexEditor::find(ByteBuffer& needle, size_t start) { - if (m_buffer.is_empty()) - return {}; - - auto raw_offset = memmem(m_buffer.data() + start, m_buffer.size() - start, needle.data(), needle.size()); - if (raw_offset == NULL) + if (m_document->size() == 0) return {}; - int relative_offset = static_cast<const u8*>(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(); + 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; + } + } + } - return end_of_match; + return {}; } Vector<Match> HexEditor::find_all(ByteBuffer& needle, size_t start) { - if (m_buffer.is_empty()) + if (m_document->size() == 0 || needle.size() == 0) return {}; Vector<Match> 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<const u8*>(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<Match> HexEditor::find_all(ByteBuffer& needle, size_t start) Vector<Match> HexEditor::find_all_strings(size_t min_length) { - if (m_buffer.is_empty()) + if (m_document->size() == 0) return {}; Vector<Match> 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<Match> 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 <AK/ByteBuffer.h> #include <AK/Function.h> @@ -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<Core::File> 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<size_t, u8> m_tracked_changes; size_t m_position { 0 }; bool m_cursor_at_low_nibble { false }; EditMode m_edit_mode { Hex }; NonnullRefPtr<Core::Timer> m_blink_timer; bool m_cursor_blink_active { false }; + NonnullOwnPtr<HexDocument> 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 <LibGUI/Model.h> struct Match { - int offset; + u64 offset; String value; }; |