Workspaces / document saving/opening
This commit is contained in:
parent
eb0077c849
commit
030f0e6438
18
index.html
18
index.html
@ -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>
|
@ -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
|
||||
}
|
||||
|
@ -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'))
|
||||
},
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
"nativeAllowList": [
|
||||
"app.*",
|
||||
"os.*",
|
||||
"filesystem.*",
|
||||
"debug.log"
|
||||
],
|
||||
"globalVariables": {},
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -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; }
|
||||
|
167
src/qwe.js
167
src/qwe.js
@ -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()
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user