Watch buffer file for changes, and automatically reload it if changed (#76)

* Implement Buffer class in main process that watches for changes to the file, and notifies the editor in the renderer process so that it can update the buffer.

* Add Editor.setReadOnly() method

* Add dummy onChangeCallback function

* Remove debug logging
This commit is contained in:
Jonatan Heyman 2024-01-01 19:04:40 +01:00 committed by GitHub
parent 0b6a1a49e8
commit 4274e6237b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 10 deletions

View File

@ -1,6 +1,8 @@
import fs from "fs" import fs from "fs"
import { join } from "path" import { join, dirname, basename } from "path"
import { app } from "electron" import { app } from "electron"
import * as jetpack from "fs-jetpack";
import CONFIG from "../config" import CONFIG from "../config"
import { isDev } from "../detect-platform" import { isDev } from "../detect-platform"
@ -21,3 +23,60 @@ export function getBufferFilePath() {
return bufferFilePath return bufferFilePath
} }
} }
export class Buffer {
constructor({filePath, onChange}) {
this.filePath = filePath
this.onChange = onChange
this.watcher = null
this.setupWatcher()
this._lastSavedContent = null
}
async load() {
const content = await jetpack.read(this.filePath, 'utf8')
this.setupWatcher()
return content
}
async save(content) {
this._lastSavedContent = content
const saveResult = await jetpack.write(this.filePath, content, {
atomic: true,
mode: '600',
})
return saveResult
}
exists() {
return jetpack.exists(this.filePath) === "file"
}
setupWatcher() {
if (!this.watcher && this.exists()) {
this.watcher = fs.watch(
dirname(this.filePath),
{
persistent: true,
recursive: false,
encoding: "utf8",
},
async (eventType, filename) => {
if (filename !== basename(this.filePath)) {
return
}
// read the file content and compare it to the last saved content
// (if the content is the same, then we can ignore the event)
const content = await jetpack.read(this.filePath, 'utf8')
if (this._lastSavedContent !== content) {
// file has changed on disk, trigger onChange
this.onChange({filename, eventType, content})
}
}
)
}
}
}

View File

@ -11,7 +11,7 @@ import { onBeforeInputEvent } from "../keymap"
import { isDev } from '../detect-platform'; import { isDev } from '../detect-platform';
import { initializeAutoUpdate, checkForUpdates } from './auto-update'; import { initializeAutoUpdate, checkForUpdates } from './auto-update';
import { fixElectronCors } from './cors'; import { fixElectronCors } from './cors';
import { getBufferFilePath } from './buffer'; import { getBufferFilePath, Buffer } from './buffer';
// The built directory structure // The built directory structure
@ -210,20 +210,24 @@ ipcMain.handle('dark-mode:set', (event, mode) => {
ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource) ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)
const buffer = new Buffer({
filePath: getBufferFilePath(),
onChange: (eventData) => {
win?.webContents.send("buffer-content:change", eventData)
},
})
ipcMain.handle('buffer-content:load', async () => { ipcMain.handle('buffer-content:load', async () => {
let bufferPath = getBufferFilePath() if (buffer.exists()) {
if (jetpack.exists(bufferPath) === "file") { return await buffer.load()
return await jetpack.read(bufferPath, 'utf8')
} else { } else {
return isDev? initialDevContent : initialContent return isDev? initialDevContent : initialContent
} }
}); });
async function save(content) { async function save(content) {
return await jetpack.write(getBufferFilePath(), content, { return await buffer.save(content)
atomic: true,
mode: '600',
})
} }
ipcMain.handle('buffer-content:save', async (event, content) =>  { ipcMain.handle('buffer-content:save', async (event, content) =>  {

View File

@ -53,6 +53,10 @@ contextBridge.exposeInMainWorld("heynote", {
async saveAndQuit(content) { async saveAndQuit(content) {
return await ipcRenderer.invoke("buffer-content:saveAndQuit", content) return await ipcRenderer.invoke("buffer-content:saveAndQuit", content)
}, },
onChangeCallback(callback) {
ipcRenderer.on("buffer-content:change", callback)
},
}, },
settings: CONFIG.get("settings"), settings: CONFIG.get("settings"),

View File

@ -21,6 +21,8 @@
}, },
}, },
components: {},
data() { data() {
return { return {
syntaxTreeDebugContent: null, syntaxTreeDebugContent: null,
@ -44,11 +46,16 @@
// load buffer content and create editor // load buffer content and create editor
window.heynote.buffer.load().then((content) => { window.heynote.buffer.load().then((content) => {
let diskContent = content
this.editor = new HeynoteEditor({ this.editor = new HeynoteEditor({
element: this.$refs.editor, element: this.$refs.editor,
content: content, content: content,
theme: this.theme, theme: this.theme,
saveFunction: (content) => { saveFunction: (content) => {
if (content === diskContent) {
return
}
diskContent = content
window.heynote.buffer.save(content) window.heynote.buffer.save(content)
}, },
keymap: this.keymap, keymap: this.keymap,
@ -57,6 +64,12 @@
}) })
window._heynote_editor = this.editor window._heynote_editor = this.editor
window.document.addEventListener("currenciesLoaded", this.onCurrenciesLoaded) window.document.addEventListener("currenciesLoaded", this.onCurrenciesLoaded)
// set up buffer change listener
window.heynote.buffer.onChangeCallback((event, {filename, content, eventType}) => {
diskContent = content
this.editor.setContent(content)
})
}) })
// set up window close handler that will save the buffer and quit // set up window close handler that will save the buffer and quit
window.heynote.onWindowClose(() => { window.heynote.onWindowClose(() => {
@ -142,7 +155,7 @@
</div> </div>
</template> </template>
<style lang="sass"> <style lang="sass" scoped>
.debug-syntax-tree .debug-syntax-tree
position: absolute position: absolute
top: 0 top: 0

View File

@ -48,6 +48,7 @@ export class HeynoteEditor {
this.lineNumberCompartmentPre = new Compartment this.lineNumberCompartmentPre = new Compartment
this.lineNumberCompartment = new Compartment this.lineNumberCompartment = new Compartment
this.foldGutterCompartment = new Compartment this.foldGutterCompartment = new Compartment
this.readOnlyCompartment = new Compartment
this.deselectOnCopy = keymap === "emacs" this.deselectOnCopy = keymap === "emacs"
const state = EditorState.create({ const state = EditorState.create({
@ -60,6 +61,8 @@ export class HeynoteEditor {
this.lineNumberCompartment.of(showLineNumberGutter ? [lineNumbers(), blockLineNumbers] : []), this.lineNumberCompartment.of(showLineNumberGutter ? [lineNumbers(), blockLineNumbers] : []),
customSetup, customSetup,
this.foldGutterCompartment.of(showFoldGutter ? [foldGutter()] : []), this.foldGutterCompartment.of(showFoldGutter ? [foldGutter()] : []),
this.readOnlyCompartment.of([]),
this.themeCompartment.of(theme === "dark" ? heynoteDark : heynoteLight), this.themeCompartment.of(theme === "dark" ? heynoteDark : heynoteLight),
heynoteBase, heynoteBase,
@ -135,6 +138,12 @@ export class HeynoteEditor {
this.view.focus() this.view.focus()
} }
setReadOnly(readOnly) {
this.view.dispatch({
effects: this.readOnlyCompartment.reconfigure(readOnly ? [EditorState.readOnly.of(true)] : []),
})
}
setTheme(theme) { setTheme(theme) {
this.view.dispatch({ this.view.dispatch({
effects: this.themeCompartment.reconfigure(theme === "dark" ? heynoteDark : heynoteLight), effects: this.themeCompartment.reconfigure(theme === "dark" ? heynoteDark : heynoteLight),

View File

@ -47,6 +47,10 @@ const Heynote = {
async saveAndQuit(content) { async saveAndQuit(content) {
}, },
onChangeCallback(callback) {
},
}, },
onWindowClose(callback) { onWindowClose(callback) {