diff --git a/firmware/application/apps/ui_text_editor.cpp b/firmware/application/apps/ui_text_editor.cpp index db6f0af5..892cadb9 100644 --- a/firmware/application/apps/ui_text_editor.cpp +++ b/firmware/application/apps/ui_text_editor.cpp @@ -341,7 +341,7 @@ static void show_save_prompt( std::function on_save, std::function continuation) { nav.display_modal( - "Save?", "Save changes?", YESNO, + "Save?", " Save changes?", YESNO, [on_save](bool choice) { if (choice && on_save) on_save(); @@ -474,7 +474,13 @@ void TextEditorView::open_file(const fs::path& path) { path_ = {}; file_dirty_ = false; has_temp_file_ = false; - auto result = FileWrapper::open(path); + auto result = FileWrapper::open( + path, false, [](uint32_t value, uint32_t total) { + Painter p; + auto percent = (value * 100) / total; + auto width = (percent * screen_width) / 100; + p.draw_hline({0, 16}, width, Color::yellow()); + }); if (!result) { nav_.display_modal("Read Error", "Cannot open file:\n" + result.error().what()); @@ -582,6 +588,10 @@ void TextEditorView::prepare_for_write() { if (has_temp_file_) return; + // TODO: This would be nice to have but it causes a stack overflow in an ISR? + // Painter p; + // p.draw_string({2, 48}, Styles::yellow, "Creating temporary file..."); + // Copy to temp file on write. has_temp_file_ = true; delete_temp_file(path_); diff --git a/firmware/application/file_wrapper.hpp b/firmware/application/file_wrapper.hpp index 02d33f86..1c120430 100644 --- a/firmware/application/file_wrapper.hpp +++ b/firmware/application/file_wrapper.hpp @@ -26,6 +26,7 @@ #include "file.hpp" #include "optional.hpp" +#include #include #include @@ -75,6 +76,8 @@ class BufferWrapper { } virtual ~BufferWrapper() {} + std::function on_read_progress{}; + /* Prevent copies */ BufferWrapper(const BufferWrapper&) = delete; BufferWrapper& operator=(const BufferWrapper&) = delete; @@ -234,13 +237,23 @@ class BufferWrapper { line_count_ = start_line_; Offset offset = start_offset_; + + // Report progress every N lines. + constexpr auto report_interval = 100u; auto result = next_newline(offset); + auto next_report = report_interval; while (result) { ++line_count_; if (newlines_.size() < max_newlines) newlines_.push_back(*result); offset = *result + 1; + + if (on_read_progress && line_count_ > next_report) { + on_read_progress(offset, size()); + next_report = line_count_ + report_interval; + } + result = next_newline(offset); } } @@ -397,6 +410,9 @@ class BufferWrapper { // Number of bytes left to shift. Offset remaining = size() - src; Offset offset = size(); + Size report_total = remaining; + Size report_interval = report_total / 8; + Size next_report = remaining - report_interval; while (remaining > 0) { offset -= std::min(remaining, buffer_size); @@ -413,6 +429,11 @@ class BufferWrapper { break; remaining -= *result; + + if (on_read_progress && remaining <= next_report) { + on_read_progress(report_total - remaining, report_total); + next_report = remaining > report_interval ? remaining - report_interval : 0; + } } } @@ -424,6 +445,9 @@ class BufferWrapper { char buffer[buffer_size]; auto offset = src; + Size report_total = size(); + Size report_interval = report_total / 8; + Size next_report = offset + report_interval; while (true) { wrapped_->seek(offset); @@ -438,6 +462,11 @@ class BufferWrapper { break; offset += *result; + + if (on_read_progress && offset >= next_report) { + on_read_progress(offset, report_total); + next_report = offset + report_interval; + } } // Delete the extra bytes at the end of the file. @@ -463,13 +492,19 @@ class FileWrapper : public BufferWrapper { template using Result = File::Result; using Error = File::Error; - static Result> open(const std::filesystem::path& path, bool create = false) { + static Result> open( + const std::filesystem::path& path, + bool create = false, + std::function on_read_progress = nullptr) { auto fw = std::unique_ptr(new FileWrapper()); auto error = fw->file_.open(path, /*read_only*/ false, create); if (error) return *error; + if (on_read_progress) + fw->on_read_progress = on_read_progress; + fw->initialize(); return fw; } diff --git a/firmware/test/application/test_file_wrapper.cpp b/firmware/test/application/test_file_wrapper.cpp index 1b179df9..7a121eb1 100644 --- a/firmware/test/application/test_file_wrapper.cpp +++ b/firmware/test/application/test_file_wrapper.cpp @@ -435,4 +435,29 @@ SCENARIO("Delete line.") { } } +SCENARIO("It calls on_read_progress while reading.") { + GIVEN("A file larger than internal buffer_size (512)") { + std::string content = std::string(599, 'a'); + content.push_back('x'); + MockFile f{content}; + + auto w = wrap_buffer(f); + auto init_line_count = w.line_count(); + auto init_size = w.size(); + auto called = false; + + w.on_read_progress = [&called](auto, auto) { + called = true; + }; + + WHEN("Replacing range with larger size") { + w.replace_range({0, 2}, "bbb"); + + THEN("callback should be called.") { + CHECK(called); + } + } + } +} + TEST_SUITE_END(); \ No newline at end of file