FileBrowser Cursor !

This commit is contained in:
chk
2026-06-14 22:21:00 +02:00
parent fb6453e2e4
commit ed19c531a8
4 changed files with 39 additions and 27 deletions

View File

@@ -1,12 +1,10 @@
/**
* Aktives Programm + Cursor — Single Source of Truth.
*
* Der Cursor lebt zur Laufzeit als In-Memory-Index (schnelles Stepping ohne
* Datei-Neuschreiben). Beim Speichern wird er ins .json-Sidecar geschrieben;
* das .gcode bleibt reiner G-Code ohne '!'-Marker.
*
* Inhaltliche Änderungen (FPoint, Editieren) werden direkt persistiert (durables
* Teaching); reine Cursor-Bewegungen NICHT.
* Der Cursor lebt zur Laufzeit als In-Memory-Index. Bei jeder Cursor-Bewegung
* (Stepping) UND bei Inhaltänderungen (FPoint, Editieren) wird er als ';!'-Marker
* in der .gcode-Datei persistiert — so bleibt die Position nach einem Neustart
* erhalten und ist direkt im File sichtbar.
*/
const store = require('../store/fileStore');
const units = require('../gcode/units');
@@ -111,9 +109,9 @@ class ActiveState {
return this.getState();
}
// ---- Stepping (reine Cursor-Bewegung, gibt die ausführbare Zeile zurück) ----
// ---- Stepping (persistiert Cursor als ';!' im .gcode) ----
_gotoIndex(index) {
async _gotoIndex(index) {
if (!this.lines.length) throw new ApiError(409, 'EMPTY_PROGRAM', 'active program is empty');
if (index < 0 || index >= this.lines.length) {
throw new ApiError(
@@ -124,6 +122,7 @@ class ActiveState {
}
this.cursor = index;
this._touch();
await this._persist(); // Cursor als ';!' in .gcode schreiben
return { cursor: this.cursor, line: units.toExecutable(this.lines[this.cursor]) };
}

View File

@@ -1,8 +1,12 @@
/**
* Datei-basierte Persistenz der Programme:
* <id>.<ext> — G-Code (Grad), standardnah, Zeitstempel/Cursor im Kommentar
* <id>.<ext> — G-Code (Grad), standardnah; Zeitstempel als ;<epoch>-Kommentar,
* Cursor-Zeile zusätzlich mit '!' (z. B. ;1234567890!)
* <id>.json — Sidecar mit Metadaten (Name, Zeiten, lineCount, angleUnit)
*
* Cursor-Primärquelle ist das ';!'-Marker im .gcode (lesbar, portierbar).
* Der Sidecar enthält cursor als Fallback und für schnellen Zugriff.
*
* Nach außen werden Programme NUR über die id angesprochen — niemals über Pfade.
* Storage-Details bleiben hier gekapselt (Konzept §8/§10).
*/
@@ -70,20 +74,21 @@ async function read(id) {
} catch {
/* Sidecar ist optional */
}
// Migration: alter '!'-Cursor-Marker in .gcode → Cursor liegt jetzt im .json.
let legacyCursor = null;
// Primärquelle: ';!'-Marker im .gcode (genau einer pro Datei).
// Fallback: cursor aus .json (ältere Dateien ohne Marker).
let fileCursor = null;
const lines = splitLines(text).map((line, i) => {
if (units.hasCursorMarker(line)) { legacyCursor = i; return units.removeCursorMarker(line); }
if (units.hasCursorMarker(line)) { fileCursor = i; return units.removeCursorMarker(line); }
return line;
});
const cursor = meta.cursor ?? legacyCursor ?? 0;
const cursor = fileCursor ?? meta.cursor ?? 0;
return { id, name: meta.name || id, cursor, lines, meta };
}
/**
* Schreibt .gcode + .json.
* lines = saubere G-Code-Zeilen (Grad, ohne '!'-Cursor-Marker).
* cursor = Cursor-Index (landet im .json, nicht in .gcode).
* lines = saubere G-Code-Zeilen (Grad, ohne '!'-Marker) — der Marker wird hier gesetzt.
* cursor = Cursor-Index: die entsprechende Zeile bekommt im .gcode ein '!' angehängt.
*/
async function write(id, { name, lines, cursor = 0 }) {
assertValidId(id);
@@ -105,7 +110,11 @@ async function write(id, { name, lines, cursor = 0 }) {
createdAt,
updatedAt: now,
};
const body = lines.join('\n') + (lines.length ? '\n' : '');
// Cursor-Zeile im .gcode mit ';!'-Marker — sichtbar in jedem Text-Editor.
const withCursor = lines.map((line, i) =>
i === cursor && line.length > 0 ? units.addCursorMarker(line) : line
);
const body = withCursor.join('\n') + (withCursor.length ? '\n' : '');
await fsp.writeFile(gcodePath(id), body, 'utf8');
await fsp.writeFile(jsonPath(id), JSON.stringify(meta, null, 2) + '\n', 'utf8');
log.info(`write ${gcodePath(id)} (${lines.length} Zeilen, cursor ${cursor})`);