using afIoc
using afReflux
using syntax
using gfx
using fwt
** (View) -
** A text editor with syntax highlighting. Borrowed from [fluxtext]`fandoc:/fluxText`.
class TextEditor : View {
@Inject private Registry registry
@Inject private Explorer explorer
@Inject private Reflux reflux
@Inject private AppStash stash
@Inject private GlobalCommands globalCommands
private EdgePane edgePane
** The 'File' being edited.
File? file
internal TextEditorPrefs options := TextEditorPrefs.load
internal Charset charset := options.charset
internal SyntaxRules? rules
internal RichText? richText
internal TextDoc? doc
private TextEditorController? controller
internal FindBar find
internal DateTime? fileTimeAtLoad
internal Label caretField := Label()
internal Label charsetField := Label()
Bool wordWrap {
set {
&wordWrap = it
stashPrefs
newWidgets
restorePrefs
}
}
protected new make(GlobalCommands globCmds, |This| in) : super(in) {
find = registry.autobuild(FindBar#, [this])
content = edgePane = EdgePane {
it.top = buildToolBar
it.bottom = buildStatusBar
}
&wordWrap = globCmds["afExplorer.cmdWordWrap"].command.selected
}
@NoDoc
override Void onActivate() {
globalCommands["afExplorer.cmdFind" ].addInvoker("afReflux.textEditor", |Event? e| { find.showFind } )
globalCommands["afExplorer.cmdFind" ].addEnabler("afReflux.textEditor", | ->Bool| { true } )
globalCommands["afExplorer.cmdFindNext" ].addInvoker("afReflux.textEditor", |Event? e| { find.next } )
globalCommands["afExplorer.cmdFindNext" ].addEnabler("afReflux.textEditor", | ->Bool| { true } )
globalCommands["afExplorer.cmdFindPrev" ].addInvoker("afReflux.textEditor", |Event? e| { find.prev } )
globalCommands["afExplorer.cmdFindPrev" ].addEnabler("afReflux.textEditor", | ->Bool| { true } )
globalCommands["afExplorer.cmdReplace" ].addInvoker("afReflux.textEditor", |Event? e| { find.showFindReplace } )
globalCommands["afExplorer.cmdReplace" ].addEnabler("afReflux.textEditor", | ->Bool| { true } )
globalCommands["afExplorer.cmdGoto" ].addInvoker("afReflux.textEditor", |Event? e| { controller?.onGoto(e) } )
globalCommands["afExplorer.cmdGoto" ].addEnabler("afReflux.textEditor", | ->Bool| { true } )
restorePrefs
}
@NoDoc
override Void onDeactivate() {
globalCommands["afExplorer.cmdFind" ].removeInvoker("afReflux.textEditor")
globalCommands["afExplorer.cmdFind" ].removeEnabler("afReflux.textEditor")
globalCommands["afExplorer.cmdFindNext" ].removeInvoker("afReflux.textEditor")
globalCommands["afExplorer.cmdFindNext" ].removeEnabler("afReflux.textEditor")
globalCommands["afExplorer.cmdFindPrev" ].removeInvoker("afReflux.textEditor")
globalCommands["afExplorer.cmdFindPrev" ].removeEnabler("afReflux.textEditor")
globalCommands["afExplorer.cmdReplace" ].removeInvoker("afReflux.textEditor")
globalCommands["afExplorer.cmdReplace" ].removeEnabler("afReflux.textEditor")
globalCommands["afExplorer.cmdGoto" ].removeInvoker("afReflux.textEditor")
globalCommands["afExplorer.cmdGoto" ].removeEnabler("afReflux.textEditor")
stashPrefs
}
@NoDoc
override Void load(Resource resource) {
super.load(resource)
file = (resource as FileResource).file
// load the document into memory
loadDoc
charsetField.text = charset.toStr
newWidgets
restorePrefs
}
private Void newWidgets() {
// create rich text widget
richText = RichText { model = doc; border = false; wrap = wordWrap }
richText.font = options.font
richText.tabSpacing = options.tabSpacing
// initialize controller
controller = registry.autobuild(TextEditorController#, [this])
controller.register
controller.updateCaretStatus
edgePane.center = richText
edgePane.relayout
}
private Void stashPrefs() {
// we want to keep the scroll pos when switching between views,
// but clear it when closing the tab - so when re-opened, we're at the top again!
if (stash["${resource?.uri}.textEditor.clear"] == true)
stash.remove("${resource?.uri}.textEditor.clear")
else {
// save viewport and caret position
stash["${resource?.uri}.textEditor.caretOffset"] = richText.caretOffset
stash["${resource?.uri}.textEditor.topLine"] = richText.topLine
}
}
private Void restorePrefs() {
if (richText == null) return
// restore viewport and caret position
caretOffset := stash["${resource?.uri}.textEditor.caretOffset"]
topLine := stash["${resource?.uri}.textEditor.topLine"]
if (caretOffset != null) richText.caretOffset = caretOffset
if (topLine != null) richText.topLine = topLine
richText.focus
}
@NoDoc
override Void save() {
out := file.out { it.charset = this.charset }
try doc.save(out)
finally out.close
fileTimeAtLoad = file.modified
super.save
}
@NoDoc
override Bool confirmClose(Bool force) {
stash.remove("${resource?.uri}.textEditor.caretOffset")
stash.remove("${resource?.uri}.textEditor.topLine")
stash["${resource?.uri}.textEditor.clear"] = true
if (!isDirty) return true
if (force) {
r := Dialog.openQuestion(reflux.window,"Save changes to $resource.name?", [Dialog.yes, Dialog.no])
if (r == Dialog.yes) save
} else {
r := Dialog.openQuestion(reflux.window, "Save changes to $resource.name?\n\nClick 'Cancel' to continue editing.", [Dialog.yes, Dialog.no, Dialog.cancel])
if (r == Dialog.cancel) return false
if (r == Dialog.yes) save
}
// clear flag to reset the tab text, because the tab (not this view panel) gets reused if we're switching views
isDirty = false
return true
}
internal Void loadDoc() {
// read document into memory, if we fail with the
// configured charset, then fallback to ISO 8859-1
// which will always "work" since it is byte based
lines := readAllLines
if (lines == null) {
charset = Charset.fromStr("ISO-8859-1")
lines = readAllLines
}
// save this time away to check on focus events
fileTimeAtLoad = file.modified
// figure out what syntax file to use
// based on file extension and shebang
rules = SyntaxRules.loadForFile(file, lines.first) ?: SyntaxRules()
// load document
doc = TextDoc(options, rules)
doc.load(lines)
}
private Str[]? readAllLines() {
in := file.in { it.charset = this.charset }
try return in.readAllLines
catch return null
finally in.close
}
private Widget buildToolBar() {
return EdgePane {
// top = InsetPane(4,4,5,4) {
// ToolBar {
// addCommand(globalCommands["afReflux.cmdSave"].command)
// addSep
//// addCommand(frame.command(CommandId.cut))
//// addCommand(frame.command(CommandId.copy))
//// addCommand(frame.command(CommandId.paste))
//// addSep
//// addCommand(frame.command(CommandId.undo))
//// addCommand(frame.command(CommandId.redo))
// },
// }
bottom = find
}
}
private Widget buildStatusBar() {
return GridPane {
it.numCols = 2
it.hgap = 10
it.halignPane = Halign.right
it.add(caretField)
it.add(charsetField)
}
}
}