mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 23:17:46 +00:00
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.
This commit is contained in:
parent
60c3ad9ae8
commit
fd66dda1d7
7 changed files with 362 additions and 117 deletions
|
@ -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
|
||||
|
|
154
Userland/Applications/HexEditor/HexDocument.cpp
Normal file
154
Userland/Applications/HexEditor/HexDocument.cpp
Normal file
|
@ -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;
|
||||
}
|
74
Userland/Applications/HexEditor/HexDocument.h
Normal file
74
Userland/Applications/HexEditor/HexDocument.h
Normal file
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
||||
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<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())
|
||||
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<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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <LibGUI/Model.h>
|
||||
|
||||
struct Match {
|
||||
int offset;
|
||||
u64 offset;
|
||||
String value;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue