feat(editor): add .typoena.toml prefs and palette settings (v0.5 slice 4)

Git-tracked editor preferences read at boot and toggled live on-device:

- Prefs type (line-based TOML parse/serialize, no crate on xtensa) held on
  Editor; firmware reads /sd/repo/.typoena.toml before the first render and
  falls back to per-key defaults. Keys: save_on_idle, format_on_save,
  line_numbers (bool) + auto_sync (string, schema/default only until v0.7).
- line_numbers applied live (gutter_cols -> 0 when off).
- Palette > command mode toggles the three bools; the list stays open so
  several flip in one visit, and :settings opens it directly. Each toggle
  applies live and queues Effect::SavePrefs (host atomic-writes the file,
  which rides the next :sync).
- save_on_idle honoured host-side as a silent, unformatted idle auto-save.
- to_toml is newline-free; save_path now appends exactly one terminator
  unconditionally so buffers round-trip byte-for-byte (trailing blanks kept).
- Firmware 0.4.0 -> 0.5.0; new docs/typoena-toml.md reference. Also refreshes
  the slice-3 macroplan status (delete fix was confirmed on device).
This commit is contained in:
Julien Calixte
2026-07-12 01:48:10 +02:00
parent 82f305cea6
commit c535864ee7
8 changed files with 862 additions and 75 deletions

View File

@@ -308,15 +308,17 @@ impl Storage {
.with_context(|| format!("create {tmp} (does its directory exist?)"))?;
f.write_all(contents.as_bytes())
.with_context(|| format!("write {tmp}"))?;
// End the file with exactly one newline (POSIX text convention; keeps git
// from flagging "No newline at end of file"). The editor buffer is
// newline-free by design, so this is the single place the terminator is
// added; `load_path` strips it back off on the way in. Guarded so an
// already-terminated buffer (e.g. an unformatted external file) isn't
// doubled.
if !contents.ends_with('\n') {
f.write_all(b"\n").with_context(|| format!("write final newline to {tmp}"))?;
}
// Append exactly one POSIX terminator, unconditionally — the symmetric
// inverse of `load_path`, which always strips one back off. This keeps
// git from flagging "No newline at end of file" and makes the buffer
// round-trip byte-for-byte: a buffer that ends in '\n' (a trailing blank
// line the writer left) becomes "…\n\n" on disk, so that blank line
// survives the next load instead of being swallowed. Everything handed
// to `save_path` is content *without* its terminator by convention (the
// editor buffer is newline-free, and `Prefs::to_toml` omits its trailing
// newline for the same reason), so this never double-terminates.
f.write_all(b"\n")
.with_context(|| format!("write final newline to {tmp}"))?;
// FatFS f_sync — flush the tmp fully before it can replace the target.
f.sync_all().with_context(|| format!("fsync {tmp}"))?;
}