@@ -18,6 +18,7 @@ import {
1818 CompletionContext,
1919 hoverTooltip,
2020 acceptCompletion,
21+ rectangularSelection,
2122} from "../../third_party/codemirror/cm.js";
2223import { ABC2SVG_DECORATIONS } from "./abc_decorations_abc2svg.js";
2324import { initSettings } from "./settings.js";
@@ -235,6 +236,7 @@ const abcCompletionCompartment = new Compartment();
235236const abcHoverCompartment = new Compartment();
236237const abcTuningModeCompartment = new Compartment();
237238const abcPayloadReadOnlyCompartment = new Compartment();
239+ const UNTITLED_UNSAVED_LABEL = "Untitled (unsaved)";
238240
239241function buildAbcCompletionSource() {
240242 const keyOptions = [
@@ -4559,16 +4561,16 @@ function setFileNameMeta(name) {
45594561 updateWindowTitle();
45604562}
45614563
4562- function updateWindowTitle() {
4563- const tuneDirty = Boolean(currentDoc && currentDoc.dirty);
4564- const dirtyTag = (tuneDirty || headerDirty) ? "*" : "";
4565- const filePath = (currentDoc && currentDoc.path) ? String(currentDoc.path) : "";
4566- const fileNameWithExt = filePath ? safeBasename(filePath) : "Untitled.abc" ;
4567- const dirPath = filePath ? safeDirname(filePath) : (libraryIndex && libraryIndex.root ? String(libraryIndex.root) : "");
4568- const dirShort = formatPathTail(dirPath, 3);
4569- const display = dirShort ? `${dirShort}/${fileNameWithExt}` : fileNameWithExt;
4570- document.title = `ABCarus — ${display}${dirtyTag}`;
4571- }
4564+ function updateWindowTitle() {
4565+ const tuneDirty = Boolean(currentDoc && currentDoc.dirty) || Boolean(isNewTuneDraft );
4566+ const dirtyTag = (tuneDirty || headerDirty) ? "*" : "";
4567+ const filePath = (currentDoc && currentDoc.path) ? String(currentDoc.path) : "";
4568+ const fileNameWithExt = filePath ? safeBasename(filePath) : UNTITLED_UNSAVED_LABEL ;
4569+ const dirPath = filePath ? safeDirname(filePath) : (libraryIndex && libraryIndex.root ? String(libraryIndex.root) : "");
4570+ const dirShort = formatPathTail(dirPath, 3);
4571+ const display = dirShort ? `${dirShort}/${fileNameWithExt}` : fileNameWithExt;
4572+ document.title = `ABCarus — ${display}${dirtyTag}`;
4573+ }
45724574
45734575function buildTuneMetaLabel(metadata) {
45744576 if (!metadata) return "Untitled";
@@ -11454,6 +11456,9 @@ function initEditor() {
1145411456 ]);
1145511457 const updateListener = EditorView.updateListener.of((update) => {
1145611458 if (update.docChanged) {
11459+ if (!suppressDirty && !payloadMode && !currentDoc) {
11460+ currentDoc = createBlankDocument();
11461+ }
1145711462 shouldHandleTypingPreviewChange(update);
1145811463 abRevision += 1;
1145911464 if (abPlan) clearAbPlan({ toast: true });
@@ -11524,6 +11529,7 @@ function initEditor() {
1152411529 doc: DEFAULT_ABC,
1152511530 extensions: [
1152611531 basicSetup,
11532+ createRectSelectionExtension(),
1152711533 abcHighlightCompartment.of([abcHighlight]),
1152811534 abcDiagnosticsCompartment.of([
1152911535 measureErrorPlugin,
@@ -11768,6 +11774,7 @@ function initHeaderEditor() {
1176811774 doc: "",
1176911775 extensions: [
1177011776 basicSetup,
11777+ createRectSelectionExtension(),
1177111778 abcHighlight,
1177211779 keymap.of([{ key: "Mod-/", run: toggleLineComments }]),
1177311780 updateListener,
@@ -11838,8 +11845,8 @@ function setActiveTuneText(text, metadata, options = {}) {
1183811845 activeFilePath = null;
1183911846 isNewTuneDraft = false;
1184011847 refreshHeaderLayers().catch(() => {});
11841- setTuneMetaText("Untitled" );
11842- setFileNameMeta("Untitled" );
11848+ setTuneMetaText(UNTITLED_UNSAVED_LABEL );
11849+ setFileNameMeta(UNTITLED_UNSAVED_LABEL );
1184311850 if (currentDoc) {
1184411851 currentDoc.path = null;
1184511852 currentDoc.content = text || "";
@@ -17595,8 +17602,8 @@ function showEmptyState() {
1759517602 activeFilePath = null;
1759617603 clearSaveSession();
1759717604 headerDirty = false;
17598- setTuneMetaText("Untitled" );
17599- setFileNameMeta("Untitled" );
17605+ setTuneMetaText(UNTITLED_UNSAVED_LABEL );
17606+ setFileNameMeta(UNTITLED_UNSAVED_LABEL );
1760017607 clearErrors();
1760117608 setStatus("Ready");
1760217609 updateFileHeaderPanel();
@@ -19639,10 +19646,9 @@ async function confirmAbandonIfDirty(contextLabel) {
1963919646 const choice = await confirmUnsavedChanges(contextLabel);
1964019647 if (choice === "cancel") return false;
1964119648 if (choice === "dont_save") {
19642- if (hdrDirty) {
19643- showToast("Header changes are unsaved. Save or cancel.", 2600);
19644- return false;
19645- }
19649+ // Explicit discard path: user chose not to save.
19650+ headerDirty = false;
19651+ updateHeaderStateUI();
1964619652 if (tuneDirty) {
1964719653 await discardWorkingCopyChangesForActiveFile();
1964819654 }
@@ -21825,45 +21831,9 @@ async function requestQuitApplication() {
2182521831}
2182621832
2182721833async function fileClose() {
21828- if (abandonFlowInProgress) return;
21829- abandonFlowInProgress = true;
21830- const currentPath = getCurrentNavFilePath();
21831- try {
21832- const ok = await confirmAbandonIfDirty("closing this file");
21833- if (!ok) return;
21834-
21835- // Close the working copy session (best-effort).
21836- try {
21837- if (window.api && typeof window.api.closeWorkingCopy === "function") {
21838- await window.api.closeWorkingCopy();
21839- }
21840- } catch {}
21841-
21842- clearCurrentDocument();
21843- setActiveTuneText("", null);
21844- setDirtyIndicator(false);
21845- activeFilePath = null;
21846-
21847- // Activate previous file in navigation history, if available; otherwise keep empty state.
21848- if (!currentPath) return;
21849- for (let i = navFileHistory.length - 1; i >= 0; i -= 1) {
21850- const candidate = navFileHistory[i];
21851- if (!candidate) continue;
21852- if (pathsEqual(candidate, currentPath)) continue;
21853- try {
21854- if (typeof window.api?.fileExists === "function") {
21855- const exists = await window.api.fileExists(candidate);
21856- if (!exists) continue;
21857- }
21858- } catch {}
21859- try {
21860- const res = await loadLibraryFileIntoEditor(candidate);
21861- if (res && res.ok) return;
21862- } catch {}
21863- }
21864- } finally {
21865- abandonFlowInProgress = false;
21866- }
21834+ // Unified close behavior: close current file to empty state.
21835+ // Keep this wrapper for toolbar call sites.
21836+ await requestCloseDocument();
2186721837}
2186821838
2186921839async function exportMusicXml() {
@@ -30296,3 +30266,17 @@ async function maybeRunDevAutoscrollDemo() {
3029630266}
3029730267
3029830268maybeRunDevAutoscrollDemo().catch(() => {});
30269+ function createRectSelectionExtension() {
30270+ return rectangularSelection({
30271+ // Linux WMs often reserve Alt+drag for window move/resize.
30272+ // Keep Alt+drag where available, and provide Ctrl+Shift+drag as a reliable fallback.
30273+ eventFilter: (event) => Boolean(
30274+ event
30275+ && event.button === 0
30276+ && (
30277+ event.altKey
30278+ || (event.ctrlKey && event.shiftKey)
30279+ )
30280+ ),
30281+ });
30282+ }
0 commit comments