mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:07:45 +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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue