Public Access
The Notes App lists the files of /notes by their first line, newest first: n starts a note, Enter opens it, r renames its file, d deletes it after asking, s sorts by name. A new note's file is named after its first line. The editor wraps at spaces, 38 columns by 8 rows; Fn+arrows move through the wrapped text, Ctrl+A and Ctrl+E go to the ends of the line. There is no save key: the note is written five seconds after the last key, on Back, on leaving the App, when the screen turns off and before the device powers off. A save writes a temporary file and puts it in the note's place; a save cut short is put back, or offered, the next time. A note is up to 16 KB, held in one buffer reserved when it's opened: the file is read straight into it and typing never makes it grow. A failed allocation aborts on this device, and with IRC connected the largest free block is about 31 KB: a first version that copied the note once on loading restarted the device when a full note was opened with IRC connected. Editing files of any size is #47. The Storage App's text viewer gets `e`, which edits a text file up to 16 KB with the same editor unless the file is read-only. 439 host tests. Checked on the device: docs/milestones/F1.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
82 lines
3.3 KiB
C++
82 lines
3.3 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace roro::notes {
|
|
|
|
// The text of a note while it's edited (F1, Q144, Q145): UTF-8 held whole in memory, a cursor, and
|
|
// the part of it on screen. Lines wrap at spaces, `cols` characters wide; a line owns the space or
|
|
// the newline it ends with, so every byte of the text belongs to exactly one line. No index of
|
|
// lines is kept (a note of newlines alone would need twice its size): where a line starts is
|
|
// worked out from the start of its paragraph, which is never far.
|
|
class NoteText {
|
|
public:
|
|
static constexpr size_t kMaxBytes = 16 * 1024;
|
|
|
|
// Room for a full note is reserved once, so typing never has to find a bigger block of memory:
|
|
// on the device a failed allocation is the end. The second form takes over a string the
|
|
// caller filled (and reserved): a note is never in memory twice.
|
|
NoteText(int cols, int rows);
|
|
NoteText(int cols, int rows, std::string&& text);
|
|
|
|
// Copied into the buffer already held. CRLF becomes LF (Q148). False, and nothing changes, if
|
|
// it's over kMaxBytes.
|
|
bool setText(const std::string& text);
|
|
const std::string& text() const { return text_; }
|
|
size_t cursor() const { return cursor_; }
|
|
uint32_t revision() const { return revision_; } // changes with every edit: is it saved?
|
|
|
|
bool insert(uint32_t codePoint); // false: the note is full
|
|
bool insertText(const std::string& s); // all of it or nothing
|
|
void backspace();
|
|
void left();
|
|
void right();
|
|
void up();
|
|
void down();
|
|
void pageUp();
|
|
void pageDown();
|
|
void lineStart();
|
|
void lineEnd();
|
|
void toStart();
|
|
void toEnd();
|
|
|
|
// The screen, kept around the cursor: its rows (tabs as spaces, the ending newline left out),
|
|
// and where the cursor is on it, in rows and characters.
|
|
std::vector<std::string> rows();
|
|
int cursorRow();
|
|
int cursorCol();
|
|
int percent() const { return text_.empty() ? 0 : static_cast<int>(top_ * 100 / text_.size()); }
|
|
|
|
std::string firstLine() const; // without its newline, for the title and the file's name
|
|
|
|
private:
|
|
size_t nextLine(size_t start) const; // where the line after the one at `start` starts
|
|
size_t lineOf(size_t pos) const; // the start of the line `pos` is on
|
|
size_t lastSpot(size_t start) const; // the last place the cursor can be on that line
|
|
size_t atColumn(size_t start, int col) const;
|
|
int columnOf(size_t start, size_t pos) const;
|
|
bool hasLineAfter(size_t start) const;
|
|
void moved(bool keepGoal = false);
|
|
void follow(); // scrolls so the cursor is on screen
|
|
void dropCarriageReturns();
|
|
|
|
int cols_, rows_;
|
|
std::string text_;
|
|
size_t cursor_ = 0, top_ = 0;
|
|
int goal_ = -1; // the column Up and Down aim for, across short lines
|
|
uint32_t revision_ = 0;
|
|
};
|
|
|
|
// The file a new note is saved as (Q142): from its first line, "Shopping list!" -> "shopping-list",
|
|
// or "note-<stamp>" when that gives nothing. No extension, no folder.
|
|
std::string nameFromFirstLine(const std::string& firstLine, const std::string& stamp);
|
|
|
|
// A row's title in the Notes list, from the first bytes of a file: its first line that isn't
|
|
// blank, cut to `maxChars`; "" if there's none.
|
|
std::string titleFrom(const std::string& head, size_t maxChars);
|
|
|
|
} // namespace roro::notes
|