Workspaces / document saving/opening

This commit is contained in:
dakedres 2023-12-25 14:41:15 -05:00
parent eb0077c849
commit 030f0e6438
7 changed files with 215 additions and 73 deletions

View File

@ -1,18 +0,0 @@
<html>
<head>
<!-- <link rel="icon" href=".temp/editorapp/resources/icons/appIcon.png"> -->
<script async src="platforms/neutralino.js"></script>
<script async type="module" src="./editor.js"></script>
<style id="stylesheet">
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; font-size: 14px; }
nav { background: #f5f5f5; color: #6c6c6c; margin: 2px 5px }
nav input { all: unset; text-decoration: underline; font-family: sans-serif; }
.cm-editor { flex-grow: 1; outline: 1px solid #ddd; overflow-y: auto; }
</style>
</head>
</html>

View File

@ -1,9 +1,9 @@
import { EditorState, Compartment, Transaction, Annotation } from '@codemirror/state'
import { indentWithTab, undo, redo, history, defaultKeymap, historyKeymap } from '@codemirror/commands'
import { indentWithTab, undo, redo, history, defaultKeymap, historyKeymap, indentMore, indentLess } from '@codemirror/commands'
import { EditorView, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap } from '@codemirror/view'
import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete'
import { closeBrackets, autocompletion, closeBracketsKeymap, startCompletion, closeCompletion, acceptCompletion, moveCompletionSelection } from '@codemirror/autocomplete'
import { lintKeymap } from '@codemirror/lint'
export default {
@ -33,11 +33,17 @@ export default {
searchKeymap,
historyKeymap,
foldKeymap,
completionKeymap,
// completionKeymap,
lintKeymap,
undo,
redo,
Transaction,
Annotation,
defaultHighlightStyle
defaultHighlightStyle,
startCompletion,
closeCompletion,
moveCompletionSelection,
acceptCompletion,
indentMore,
indentLess
}

View File

@ -1,16 +1,15 @@
export default {
async access(path) {
try {
await Neutralino.filesystem.getStats(path)
return
} catch(err) {
if(err.name = 'NE_FS_NOPATHE') {
return false
} else {
throw err
}
}
},
{
let userHome
const handleNeutralinoError = err => {
throw new Error(err.code + ': ' + err.message)
}
window.Platform = {
//
// Paths
//
dirname(path) {
let index = path.lastIndexOf('/')
@ -28,6 +27,51 @@ export default {
let filename = this.filename(path)
let index = filename.lastIndexOf('.')
return index === -1 ? '' : path.slice(index + 1)
}
return index === -1 ? '' : filename.slice(index + 1)
},
join(...args) {
return args.join('/')
},
// TODO: support non-posix
absolute(path) {
return path.charAt(0) === '/'
},
//
// FS
//
async access(path) {
try {
await Neutralino.filesystem.getStats(path)
return
} catch(err) {
if(err.name = 'NE_FS_NOPATHE') {
return false
} else {
throw err
}
}
},
writeText(path, content) {
return Neutralino.filesystem.writeFile(path, content)
.catch(handleNeutralinoError)
},
readText(path) {
return Neutralino.filesystem.readFile(path)
.catch(handleNeutralinoError)
},
//
// OS
//
async getHome() {
return userHome ?? (userHome = await Neutralino.os.getEnv('HOME'))
},
}
}

View File

@ -15,6 +15,7 @@
"nativeAllowList": [
"app.*",
"os.*",
"filesystem.*",
"debug.log"
],
"globalVariables": {},

View File

@ -8,6 +8,13 @@ landingDocument: {
}
},
binds: {
reload: 'Ctrl-Escape',
editPath: 'Ctrl-r',
save: 'Ctrl-s',
open: 'Ctrl-o'
},
languages: [
{
exts: [ 'js', 'mjs', 'cjs', 'ejs' ],
@ -24,9 +31,7 @@ languages: [
name: 'md',
import: './lib/markdown.js',
createExtension({ markdown }) {
return markdown({
})
return markdown()
}
}
]

View File

@ -2,12 +2,15 @@
<head>
<script async src="./lib/neutralino.js"></script>
<script async src="./lib/platform.js"></script>
<script async type="module" src="./qwe.js"></script>
<style id="stylesheet">
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; font-size: 14px; }
nav { background: #f5f5f5; color: #6c6c6c; margin: 2px 5px }
nav input { all: unset; text-decoration: underline; font-family: sans-serif; }
nav #workspace { float: right; text-align: right; }
.cm-editor { flex-grow: 1; outline: 1px solid #ddd; overflow-y: auto; }
.cm-gutters { pointer-events: none; }
.cm-editor::-webkit-scrollbar, .cm-scroller::-webkit-scrollbar { background: #f5f5f5; width: 14px; height: 14px; }

View File

@ -1,17 +1,19 @@
import Platform from './lib/platform.js'
import config from './config.js'
import cm from './lib/codemirror.js'
window.cm = cm
function Editor() {
this.open = async () => {
this.init = async () => {
if(config.statusBarAtTop)
document.body.appendChild(statusbar.element)
console.log(cm, cm.EditorView)
this.workspace = await Platform.getHome()
statusbar.updateWorkspace()
this.view = new cm.EditorView({
state: await openToDocument()
state: await openLandingDocument()
.catch(console.error),
parent: document.body
})
@ -23,40 +25,72 @@ function Editor() {
this.view.focus()
}
// Opens a directory or a file
this.open = path => {
}
this.openDocument = async (path) => {
this.doc = await Doc.open(path)
.catch(err => `Failed to open "${path}": ${err}`)
this.view.setState(await this.doc.createState())
this.doc.setView(this.view)
}
this.saveDocument = () => {
let path = this.doc.getPath()
Platform.writeText(path, this.view.state.doc.toString())
.then(() => this.doc.boundPath = path)
// TODO: User interface for errors like this
.catch(err => 'Failed to save file:' + err)
}
this.setDocPath = path => {
this.doc.setPath(path)
statusbar.updateFilename()
}
// this.updateFilename = () => {
// this.doc.path = Platform.dirname(this.doc.path) + '/' + statusbar.filename.value
// }
const openToDocument = async () => {
let doc = new Doc(config.landingDocument.render(), config.landingDocument.name)
const openLandingDocument = async () => {
let doc = new Doc(config.landingDocument.render(), Platform.join(this.workspace, config.landingDocument.name))
this.doc = doc
statusbar.filename.value = config.landingDocument.name
statusbar.updateFilename()
return await doc.createState()
.catch(err => console.error("Could not open landing document", err))
}
this.doc = null
this.workspace = '/'
}
function Statusbar() {
this.selectFilename = () => {
lastFilename = this.filename.value
// lastFilename = this.filename.value
this.filename.value = editor.doc.getPath()
let path = editor.doc.getPath()
if(path.startsWith(editor.workspace))
path = path.slice(editor.workspace.length + 1)
this.filename.value = path
this.filename.select()
this.filename.selectionStart = editor.doc.getPath().lastIndexOf('/') + 1
this.filename.selectionStart = path.length - Platform.filename(path).length
}
this.element = document.createElement('nav')
this.filename = document.createElement('input')
let lastFilename = ''
this.updateFilename = () => {
this.filename.value = Platform.filename(editor.doc.getPath())
}
this.element.appendChild(this.filename)
this.updateWorkspace = () => {
this.workspace.value = Platform.filename(editor.workspace)
}
this.filename.addEventListener('click', this.selectFilename)
this.filename.addEventListener('keydown', (event) => {
const onPromptKeydown = (event) => {
switch(event.key) {
case 'Enter':
case 'Escape':
@ -64,21 +98,34 @@ function Statusbar() {
editor.view.focus()
break
}
})
}
this.filename.addEventListener('focusout', event => {
const onFilenameExit = () => {
if(this.filename.value == '') {
this.filename.value = lastFilename
this.updateFilename()
} else {
let path = this.filename.value
editor.doc.setPath(path)
this.filename.value = Platform.filename(path)
editor.setDocPath(Platform.absolute(path) ? path : Platform.join(editor.workspace, path))
}
})
}
this.element = document.createElement('nav')
this.filename = document.createElement('input')
this.workspace = document.createElement('input')
// let lastFilename = ''
this.filename.id = "filename"
this.filename.addEventListener('click', this.selectFilename)
this.filename.addEventListener('keydown', onPromptKeydown)
this.filename.addEventListener('focusout', onFilenameExit)
this.workspace.id = "workspace"
this.element.appendChild(this.filename)
this.element.appendChild(this.workspace)
}
function Doc(content, initialPath = '') {
function Doc(content, initialPath) {
// if(path) {
// let workspaces = Platform.storage.get('workspaces')
// }
@ -161,13 +208,23 @@ function Doc(content, initialPath = '') {
})
}
let path = initialPath
// This is the path in the statusbar
let path = initialPath ?? ''
let view = null
// This is where doc state info is saved
this.boundPath = initialPath
this.language = getLanguage()
}
Doc.open = async path => {
return new Doc()
Doc.open = async (path) => {
console.log('Opening ' + path)
let content = await Platform.readText(path)
.catch(console.error)
console.log(content)
return new Doc(content ?? '', path)
}
Doc.tabSize = new cm.Compartment
@ -180,11 +237,19 @@ const Keymaps = {
...cm.defaultKeymap,
...cm.searchKeymap,
...cm.foldKeymap,
...cm.completionKeymap,
...cm.lintKeymap,
cm.indentWithTab,
// Alt completion keymap that merges with tab to indent
{ key: "Ctrl-Space", run: cm.startCompletion },
{ key: "Escape", run: cm.closeCompletion },
{ key: "ArrowDown", run: cm.moveCompletionSelection(true) },
{ key: "ArrowUp", run: cm.moveCompletionSelection(false) },
{ key: "PageDown", run: cm.moveCompletionSelection(true, "page") },
{ key: "PageUp", run: cm.moveCompletionSelection(false, "page") },
{ key: "Tab", run(view, event) { event.preventDefault(); cm.acceptCompletion(view) || cm.indentMore(view) }, shift: cm.indentLess },
{
key: 'Ctrl-r',
key: config.binds.editPath,
run({ state }, event) {
event.preventDefault()
let mainSelection = state.selection.main
@ -193,6 +258,41 @@ const Keymaps = {
statusbar.filename.value = state.sliceDoc(mainSelection.from, mainSelection.to)
statusbar.selectFilename()
}
},
{
key: config.binds.open,
run({ state }) {
let mainSelection = state.selection.main
if(!mainSelection.empty) {
let path = state.sliceDoc(mainSelection.from, mainSelection.to).trim()
console.log(path)
editor.setDocPath(
Platform.absolute(path) ?
path :
Platform.join(Platform.dirname(editor.doc.getPath()), path)
)
statusbar.updateFilename()
}
editor.openDocument(editor.doc.getPath())
}
},
{
key: config.binds.save,
run() {
editor.saveDocument()
}
},
{
key: config.binds.reload,
run() {
window.location.href += ''
}
}
])
}
@ -211,8 +311,9 @@ function LanguageManager() {
}
window.addEventListener('load', () => {
window.statusbar = new Statusbar()
window.editor = new Editor()
Neutralino.init()
window.langManager = new LanguageManager()
editor.open()
window.editor = new Editor()
window.statusbar = new Statusbar()
editor.init()
})