Инкрементальный индекс, пресеты автосейва, диалог Настройки

- VaultIndex: updateFile/removeFile/syncIncremental (mtime+size), полный рескан только на старте/смене/внешних изменениях; relink висячих [[ссылок]] при создании файла; FileStore.externalChanged для внешних правок
- QML: сейвы и правки идут точечно, дерево — через syncIncremental
- Настройки: пресеты safe/balanced/eco/manual (диск 2/5/10с/выкл, коммит после паузы 5/15/60с/вручную), диалог с +/-, шкалами и подсказкой; коммит только в простое, в manual — по Ctrl+S
- Удалены highlightedCode, словный merge, fileDiffText/Html, 2-arg fileHistory; renderInner без побочек
- Тесты: инкремент индекса, пресеты, autosaveEnabled, ~~~-блоки
This commit is contained in:
2026-09-06 23:02:05 +03:00
parent 1f140d3433
commit 417d0273b7
20 changed files with 802 additions and 80 deletions
+25 -13
View File
@@ -782,23 +782,26 @@ Item {
}
refreshGitStatus()
}
function armAutoCommit(path) {
// Коммит только после паузы в печати (idle), а не на каждое сохранение
if (!settingsStore.autocommitEnabled || !fileStore.ready || !path || path.length === 0)
return
autoCommitDebounce.filePath = path
autoCommitDebounce.restart()
}
Connections {
target: documentManager
function onActiveIndexChanged() { root.loadContent() }
function onContentChanged(idx) { if (idx === documentManager.activeIndex && !textArea.activeFocus) root.loadContent(); refreshGitStatus() }
function onContentChanged(idx) {
if (idx === documentManager.activeIndex && !textArea.activeFocus) root.loadContent()
refreshGitStatus()
if (idx >= 0) root.armAutoCommit(documentManager.pathAt(idx))
}
function onOpenTabsChanged() { root.loadContent() }
function onDirtyFlagChanged() { refreshGitStatus() }
function onDocumentSaved(idx) {
refreshGitStatus()
// Авто-коммит для простого пользователя — каждый сохранённый файл попадает в историю
if (fileStore.ready && idx >= 0) {
const p = documentManager.pathAt(idx)
if (p && p.length>0) {
// Небольшая задержка чтобы не спамить на каждый автосейв
autoCommitDebounce.filePath = p
autoCommitDebounce.restart()
}
}
if (idx >= 0) root.armAutoCommit(documentManager.pathAt(idx))
}
}
Connections { target: fileStore; function onVaultChanged() { refreshGitStatus(); loadContent() } }
@@ -806,9 +809,9 @@ Item {
Timer {
id: autoCommitDebounce
property string filePath: ""
interval: 2000
interval: settingsStore.autocommitIdleMs
onTriggered: {
if (fileStore.ready && filePath.length>0) {
if (settingsStore.autocommitEnabled && fileStore.ready && filePath.length>0) {
gitManager.autoCommit(fileStore.vault, filePath)
refreshGitStatus()
}
@@ -996,7 +999,16 @@ Item {
TableEditor { id: tableEditor; onMarkdownReady: (md) => { textArea.insert(textArea.cursorPosition, md); documentManager.updateContent(documentManager.activeIndex, textArea.text) } }
Shortcut { sequence: "Ctrl+S"; onActivated: { documentManager.saveAll(); refreshGitStatus() } }
Shortcut { sequence: "Ctrl+S"; onActivated: {
const dirty = []
for (let i = 0; i < documentManager.openTabs.length; ++i)
if (documentManager.isDirty(i)) dirty.push(documentManager.pathAt(i))
documentManager.saveAll()
// В ручном режиме Ctrl+S и сохраняет, и коммитит изменённые файлы
if (!settingsStore.autosaveEnabled && fileStore.ready)
for (const p of dirty) gitManager.autoCommit(fileStore.vault, p)
refreshGitStatus()
} }
Shortcut { sequence: "Ctrl+W"; onActivated: documentManager.closeTab(documentManager.activeIndex) }
Shortcut { sequence: "Ctrl+H"; onActivated: {
const p = documentManager.pathAt(documentManager.activeIndex)
+9 -2
View File
@@ -151,6 +151,7 @@ Item {
if (drop.source && drop.source.dragPath && fileStore.ready) {
const err = fileStore.moveItem(drop.source.dragPath, fileStore.vault)
if (err.length > 0) showStatus(err, false)
else vaultIndex.syncIncremental()
}
}
}
@@ -282,6 +283,7 @@ Item {
if (!rowRect.isDir || !drop.source || !drop.source.dragPath) return
const err = fileStore.moveItem(drop.source.dragPath, rowRect.path)
if (err.length > 0) root.showStatus(err, false)
else vaultIndex.syncIncremental()
}
}
@@ -439,9 +441,14 @@ Item {
if (pendingCreate.kind==="file") err=fileStore.createFile(createIn,name)
else if (pendingCreate.kind==="folder") err=fileStore.createFolder(createIn,name)
else if (pendingCreate.kind==="rename") err=fileStore.renameItem(renameTarget.path,name)
if (err.length>0) showStatus(err,false); else if (pendingCreate.kind!=="rename") showStatus(qsTr("Создано: %1").arg(name), true)
if (err.length>0) {
showStatus(err,false)
} else {
if (pendingCreate.kind!=="rename") showStatus(qsTr("Создано: %1").arg(name), true)
vaultIndex.syncIncremental()
}
pendingCreate.parent=""
}
}
ConfirmDialog { id: deleteDialog; onConfirmed: { const err=fileStore.removeItem(deleteTarget.path); if(err.length>0) showStatus(err,false); else showStatus(qsTr("Удалено"), true) } }
ConfirmDialog { id: deleteDialog; onConfirmed: { const err=fileStore.removeItem(deleteTarget.path); if(err.length>0) showStatus(err,false); else { showStatus(qsTr("Удалено"), true); vaultIndex.syncIncremental() } } }
}
+18 -3
View File
@@ -26,16 +26,31 @@ Item {
target: fileStore
function onVaultChanged() { if (fileStore.ready) vaultIndex.rebuild(fileStore.vault) }
function onReadyChanged() { if (fileStore.ready) vaultIndex.rebuild(fileStore.vault) }
// Внешнее изменение (синхронизация/другая программа): досинхронизировать индекс
// по mtime — дёшево, без полного рескана
function onExternalChanged() { if (fileStore.ready) vaultIndex.syncIncremental() }
}
Connections {
target: documentManager
function onActiveIndexChanged() { root.refreshLinksTags() }
function onOpenTabsChanged() { root.refreshLinksTags() }
function onContentChanged(i) { if (i === documentManager.activeIndex) { root.refreshLinksTags(); rebuildDebounce.restart() } }
function onDocumentSaved() { rebuildDebounce.restart() }
function onContentChanged(i) { if (i === documentManager.activeIndex) { root.refreshLinksTags(); indexDebounce.restart() } }
function onDocumentSaved(idx, path) {
if (fileStore.ready && path && path.length > 0) vaultIndex.updateFile(path)
}
}
Connections { target: vaultIndex; function onIndexChanged() { root.refreshLinksTags() } }
Timer { id: rebuildDebounce; interval: 800; onTriggered: if (fileStore.ready) vaultIndex.rebuild(fileStore.vault) }
// Правка активного файла: обновить только его (один парс), а не всё хранилище
Timer {
id: indexDebounce
interval: 800
onTriggered: {
if (fileStore.ready && documentManager.activeIndex >= 0) {
const p = documentManager.pathAt(documentManager.activeIndex)
if (p && p.length > 0) vaultIndex.updateFile(p)
}
}
}
// Делаем панели ресайзабельными внутри правой колонки — увеличенная зона по всей ширине
SplitView {