mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-04-12 02:56:45 +02:00
501 lines
18 KiB
CoffeeScript
501 lines
18 KiB
CoffeeScript
Ant = this
|
|
|
|
class CodePad extends this.OS.GUI.BaseApplication
|
|
constructor: (args) ->
|
|
super "CodePad", args
|
|
@currfile = "Untitled".asFileHandle()
|
|
@currdir = undefined
|
|
if @args and @args.length > 0
|
|
if @args[0].type is "dir"
|
|
@currdir = @args[0].path.asFileHandle()
|
|
else
|
|
@currfile = @args[0].path.asFileHandle()
|
|
@currdir = @currfile.parent()
|
|
|
|
main: () ->
|
|
@extensions = {}
|
|
@fileview = @find("fileview")
|
|
@sidebar = @find("sidebar")
|
|
@tabbar = @find "tabbar"
|
|
@langstat = @find "langstat"
|
|
@editorstat = @find "editorstat"
|
|
|
|
@fileview.set "fetch", (path) ->
|
|
new Promise (resolve, reject) ->
|
|
dir = path
|
|
dir = path.asFileHandle() if typeof path is "string"
|
|
dir.read().then (d) ->
|
|
return reject d.error if d.error
|
|
resolve d.result
|
|
.catch (e) -> reject e
|
|
@setup()
|
|
|
|
setup: () ->
|
|
ace.config.set('basePath', '/scripts/ace')
|
|
ace.require "ace/ext/language_tools"
|
|
@editor = ace.edit @find("datarea")
|
|
@editor.setOptions {
|
|
enableBasicAutocompletion: true,
|
|
enableSnippets: true,
|
|
enableLiveAutocompletion: true,
|
|
highlightActiveLine: true,
|
|
highlightSelectedWord: true,
|
|
behavioursEnabled: true,
|
|
wrap: true,
|
|
fontSize: "11pt"
|
|
}
|
|
#themes = ace.require "ace/ext/themelist"
|
|
@editor.setTheme "ace/theme/monokai"
|
|
@modes = ace.require "ace/ext/modelist"
|
|
@editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
|
|
@editor.getSession().setUseWrapMode true
|
|
@editormux = false
|
|
@editor.on "input", () =>
|
|
if @editormux
|
|
@editormux = false
|
|
return false
|
|
if not @currfile.dirty
|
|
@currfile.dirty = true
|
|
@currfile.text += "*"
|
|
@tabbar.update()
|
|
@editor.getSession().selection.on "changeCursor", (e) =>
|
|
@updateStatus()
|
|
|
|
@tabbar.set "ontabselect", (e) =>
|
|
@selecteTab $(e.data.item).index()
|
|
@tabbar.set "ontabclose", (e) =>
|
|
it = e.data.item
|
|
return false unless it
|
|
return @closeTab it unless it.get("data").dirty
|
|
@openDialog("YesNoDialog", {
|
|
title: __("Close tab"),
|
|
text: __("Close without saving ?")
|
|
}).then (d) =>
|
|
return @closeTab it if d
|
|
@editor.focus()
|
|
return false
|
|
@fileview.set "onfileopen", (e) =>
|
|
return unless e.data and e.data.path
|
|
return if e.data.type is "dir"
|
|
@openFile e.data.path.asFileHandle()
|
|
|
|
@fileview.set "onfileselect", (e) =>
|
|
return unless e.data and e.data.path
|
|
return if e.data.type is "dir"
|
|
i = @findTabByFile e.data.path.asFileHandle()
|
|
return @tabbar.set "selected", i if i isnt -1
|
|
|
|
@on "resize", () => @editor.resize()
|
|
@on "focus", () => @editor.focus()
|
|
@spotlight = new CMDMenu __("Command palette")
|
|
@bindKey "ALT-P", () => @spotlight.run @
|
|
@find("datarea").contextmenuHandle = (e, m) =>
|
|
m.set "items", [{
|
|
text: __("Command palete"),
|
|
onmenuselect: (e) =>
|
|
@spotlight.run @
|
|
}]
|
|
m.show e
|
|
|
|
@fileview.contextmenuHandle = (e, m) =>
|
|
m.set "items", [
|
|
{ text: "__(New file)", id: "new" },
|
|
{ text: "__(New folder)", id: "newdir" },
|
|
{ text: "__(Rename)", id: "rename" },
|
|
{ text: "__(Delete)", id: "delete" }
|
|
]
|
|
m.set "onmenuselect", (e) =>
|
|
@ctxFileMenuHandle e
|
|
m.show e
|
|
|
|
@bindKey "ALT-N", () => @menuAction "new"
|
|
@bindKey "ALT-O", () => @menuAction "open"
|
|
@bindKey "ALT-F", () => @menuAction "opendir"
|
|
@bindKey "CTRL-S", () => @menuAction "save"
|
|
@bindKey "ALT-W", () => @menuAction "saveas"
|
|
|
|
@fileview.set "ondragndrop", (e) =>
|
|
src = e.data.from.get("data").path.asFileHandle()
|
|
des = e.data.to.get("data").path
|
|
src.move "#{des}/#{src.basename}"
|
|
.then (d) ->
|
|
e.data.to.update des
|
|
e.data.from.get("parent").update src.parent().path
|
|
.catch (e) => @error __("Unable to move file/folder"), e
|
|
|
|
@on "filechange", (data) =>
|
|
path = data.file.path
|
|
path = data.file.parent().path if data.type is "file"
|
|
@fileview.update path
|
|
|
|
|
|
@loadExtensionMetaData()
|
|
@initCommandPalete()
|
|
@initSideBar()
|
|
@openFile @currfile
|
|
|
|
openFile: (file) ->
|
|
#find tab
|
|
i = @findTabByFile file
|
|
return @tabbar.set "selected", i if i isnt -1
|
|
return @newTab file if file.path.toString() is "Untitled"
|
|
|
|
file.read()
|
|
.then (d) =>
|
|
file.cache = d or ""
|
|
@newTab file
|
|
.catch (e) =>
|
|
@error __("Unable to open: {0}", file.path), e
|
|
|
|
findTabByFile: (file) ->
|
|
lst = @tabbar.get "items"
|
|
its = ( i for d, i in lst when d.hash() is file.hash() )
|
|
return -1 if its.length is 0
|
|
return its[0]
|
|
|
|
newTab: (file) ->
|
|
file.text = if file.basename then file.basename else file.path
|
|
file.cache = "" unless file.cache
|
|
file.um = new ace.UndoManager()
|
|
@currfile.selected = false
|
|
file.selected = true
|
|
#console.log cnt
|
|
@tabbar.push file
|
|
|
|
closeTab: (it) ->
|
|
@tabbar.remove it
|
|
cnt = @tabbar.get("items").length
|
|
|
|
if cnt is 0
|
|
@openFile "Untitled".asFileHandle()
|
|
return false
|
|
@tabbar.set "selected", cnt - 1
|
|
return false
|
|
|
|
selecteTab: (i) ->
|
|
#return if i is @tabbar.get "selidx"
|
|
file = (@tabbar.get "items")[i]
|
|
return unless file
|
|
@scheme.set "apptitle", file.text.toString()
|
|
#return if file is @currfile
|
|
if @currfile isnt file
|
|
@currfile.cache = @editor.getValue()
|
|
@currfile.cursor = @editor.selection.getCursor()
|
|
@currfile.selected = false
|
|
@currfile = file
|
|
|
|
if not file.langmode
|
|
if file.path.toString() isnt "Untitled"
|
|
m = @modes.getModeForPath(file.path)
|
|
file.langmode = { caption: m.caption, mode: m.mode }
|
|
else
|
|
file.langmode = { caption: "Text", mode: "ace/mode/text" }
|
|
@editormux = true
|
|
@editor.getSession().setUndoManager new ace.UndoManager()
|
|
@editor.setValue file.cache, -1
|
|
@editor.getSession().setMode file.langmode.mode
|
|
if file.cursor
|
|
@editor.renderer.scrollCursorIntoView {
|
|
row: file.cursor.row, column: file.cursor.column
|
|
}, 0.5
|
|
@editor.selection.moveTo file.cursor.row, file.cursor.column
|
|
@editor.getSession().setUndoManager file.um
|
|
@updateStatus()
|
|
@editor.focus()
|
|
|
|
updateStatus: () ->
|
|
c = @editor.session.selection.getCursor()
|
|
l = @editor.session.getLength()
|
|
@editorstat.set "text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l)
|
|
@langstat.set "text", @currfile.langmode.caption
|
|
|
|
initSideBar: () ->
|
|
if @currdir
|
|
$(@sidebar).show()
|
|
@fileview.set "path", @currdir.path
|
|
else
|
|
$(@sidebar).hide()
|
|
@trigger "resize"
|
|
|
|
addAction: (action) ->
|
|
@spotlight.addAction action
|
|
@
|
|
|
|
addActions: (list) ->
|
|
@spotlight.addActions list
|
|
@
|
|
|
|
initCommandPalete: () ->
|
|
themes = ace.require "ace/ext/themelist"
|
|
cmdtheme = new CMDMenu __("Change theme")
|
|
cmdtheme.addAction { text: v.caption, theme: v.theme } for k, v of themes.themesByName
|
|
cmdtheme.onchildselect (d, r) ->
|
|
data = d.data.item.get("data")
|
|
r.editor.setTheme data.theme
|
|
r.editor.focus()
|
|
@spotlight.addAction cmdtheme
|
|
cmdmode = new CMDMenu __("Change language mode")
|
|
cmdmode.addAction { text: v.caption, mode: v.mode } for v in @modes.modes
|
|
cmdmode.onchildselect (d, r) ->
|
|
data = d.data.item.get("data")
|
|
r.editor.session.setMode data.mode
|
|
r.currfile.langmode = { caption: data.text, mode: data.mode }
|
|
r.updateStatus()
|
|
r.editor.focus()
|
|
@spotlight.addAction cmdmode
|
|
@addAction CMDMenu.fromMenu @fileMenu()
|
|
|
|
loadExtensionMetaData: () ->
|
|
"#{@meta().path}/extensions.json"
|
|
.asFileHandle()
|
|
.read("json")
|
|
.then (d) =>
|
|
for ext in d
|
|
if @extensions[ext.name]
|
|
@extensions[ext.name].child = []
|
|
@extensions[ext.name].addAction v for v in ext.actions
|
|
else
|
|
@extensions[ext.name] = new CMDMenu ext.text
|
|
@extensions[ext.name].name = ext.name
|
|
@extensions[ext.name].addAction v for v in ext.actions
|
|
@spotlight.addAction @extensions[ext.name]
|
|
@extensions[ext.name].onchildselect (e) =>
|
|
@loadAndRunExtensionAction e.data.item.get "data"
|
|
.catch (e) =>
|
|
@error __("Cannot load extension meta data"), e
|
|
|
|
runExtensionAction: (name, action) ->
|
|
return @error __("Unable to find extension: {0}", name) unless CodePad.extensions[name]
|
|
ext = new CodePad.extensions[name](@)
|
|
return @error __("Unable to find action: {0}", action) unless ext[action]
|
|
ext.preload()
|
|
.then () ->
|
|
ext[action]()
|
|
.catch (e) =>
|
|
@error __("Unable to preload extension"), e
|
|
|
|
loadAndRunExtensionAction: (data) ->
|
|
name = data.parent.name
|
|
action = data.name
|
|
#verify if the extension is load
|
|
if not CodePad.extensions[name]
|
|
#load the extension
|
|
path = "#{@meta().path}/extensions/#{name}.js"
|
|
@_api.requires path
|
|
.then () => @runExtensionAction name, action
|
|
.catch (e) =>
|
|
@error __("unable to load extension: {0}", name), e
|
|
else
|
|
@runExtensionAction name, action
|
|
|
|
fileMenu: () ->
|
|
{
|
|
text: __("File"),
|
|
child: [
|
|
{ text: __("New"), dataid: "new", shortcut: "A-N" },
|
|
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
|
|
{ text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" },
|
|
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
|
|
{ text: __("Save as"), dataid: "saveas", shortcut: "A-W" }
|
|
],
|
|
onchildselect: (e, r) =>
|
|
@menuAction e.data.item.get("data").dataid, r
|
|
}
|
|
|
|
ctxFileMenuHandle: (e) ->
|
|
el = e.data.item
|
|
return unless el
|
|
data = el.get("data")
|
|
return unless data
|
|
file = @fileview.get "selectedFile"
|
|
dir = @currdir
|
|
dir = file.path.asFileHandle() if file and file.type is "dir"
|
|
dir = file.path.asFileHandle().parent() if file and file.type is "file"
|
|
|
|
switch data.id
|
|
when "new"
|
|
return unless dir
|
|
@openDialog("PromptDialog", {
|
|
title: "__(New file)",
|
|
label: "__(File name)"
|
|
})
|
|
.then (d) =>
|
|
fp = "#{dir.path}/#{d}".asFileHandle()
|
|
fp.write("text/plain")
|
|
.then (r) =>
|
|
@fileview.update dir.path
|
|
.catch (e) =>
|
|
@error __("Fail to create: {0}", e.stack), e
|
|
|
|
when "newdir"
|
|
return unless dir
|
|
@openDialog("PromptDialog", {
|
|
title: "__(New folder)",
|
|
label: "__(Folder name)"
|
|
})
|
|
.then (d) =>
|
|
dir.mk(d)
|
|
.then (r) =>
|
|
@fileview.update dir.path
|
|
.catch (e) =>
|
|
@error __("Fail to create: {0}", dir.path), e
|
|
|
|
when "rename"
|
|
return unless file
|
|
@openDialog("PromptDialog", {
|
|
title: "__(Rename)",
|
|
label: "__(File name)",
|
|
value: file.filename
|
|
})
|
|
.then (d) =>
|
|
return if d is file.filename
|
|
file = file.path.asFileHandle()
|
|
dir = file.parent()
|
|
file.move "#{dir.path}/#{d}"
|
|
.then (r) =>
|
|
@fileview.update dir.path
|
|
.catch (e) =>
|
|
@error __("Fail to rename: {0}", file.path), e
|
|
|
|
when "delete"
|
|
return unless file
|
|
@openDialog("YesNoDialog", {
|
|
title: "__(Delete)",
|
|
iconclass: "fa fa-question-circle",
|
|
text: __("Do you really want to delete: {0}?", file.filename)
|
|
})
|
|
.then (d) =>
|
|
return unless d
|
|
file = file.path.asFileHandle()
|
|
dir = file.parent()
|
|
file.remove()
|
|
.then (r) =>
|
|
@fileview.update dir.path
|
|
.catch (e) =>
|
|
@error __("Fail to delete: {0}", file.path), e
|
|
|
|
else
|
|
|
|
|
|
save: (file) ->
|
|
file.write("text/plain")
|
|
.then (d) =>
|
|
file.dirty = false
|
|
file.text = file.basename
|
|
@tabbar.update()
|
|
@scheme.set "apptitle", "#{@currfile.basename}"
|
|
.catch (e) => @error __("Unable to save file: {0}", file.path), e
|
|
|
|
|
|
saveAs: () ->
|
|
@openDialog("FileDialog", {
|
|
title: __("Save as"),
|
|
file: @currfile
|
|
})
|
|
.then (f) =>
|
|
d = f.file.path.asFileHandle()
|
|
d = d.parent() if f.file.type is "file"
|
|
@currfile.setPath "#{d.path}/#{f.name}"
|
|
@save @currfile
|
|
|
|
menuAction: (dataid, r) ->
|
|
me = @
|
|
me = r if r
|
|
switch dataid
|
|
when "new"
|
|
me.openFile "Untitled".asFileHandle()
|
|
when "open"
|
|
me.openDialog("FileDialog", {
|
|
title: __("Open file"),
|
|
mimes: (v for v in me.meta().mimes when v isnt "dir")
|
|
})
|
|
.then (f) ->
|
|
me.openFile f.file.path.asFileHandle()
|
|
when "opendir"
|
|
me.openDialog("FileDialog", {
|
|
title: __("Open folder"),
|
|
mimes: ["dir"]
|
|
})
|
|
.then (f) ->
|
|
me.currdir = f.file.path.asFileHandle()
|
|
me.initSideBar()
|
|
when "save"
|
|
me.currfile.cache = me.editor.getValue()
|
|
return me.save me.currfile if me.currfile.basename
|
|
me.saveAs()
|
|
when "saveas"
|
|
me.currfile.cache = me.editor.getValue()
|
|
me.saveAs()
|
|
else
|
|
console.log dataid
|
|
|
|
cleanup: (evt) ->
|
|
dirties = ( v for v in @tabbar.get "items" when v.dirty )
|
|
return if dirties.length is 0
|
|
evt.preventDefault()
|
|
@.openDialog("YesNoDialog", {
|
|
title: "__(Quit)",
|
|
text: __("Ignore all unsaved files: {0} ?", (v.filename() for v in dirties).join ", " )
|
|
}).then (d) =>
|
|
if d
|
|
v.dirty = false for v in dirties
|
|
@quit()
|
|
|
|
menu: () ->
|
|
menu = [
|
|
@fileMenu()
|
|
{
|
|
text: "__(View)",
|
|
child: [
|
|
{ text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" }
|
|
],
|
|
onchildselect: (e, r) =>
|
|
@spotlight.run @
|
|
}
|
|
]
|
|
menu
|
|
|
|
class CMDMenu
|
|
constructor: (@text, @shortcut) ->
|
|
@child = []
|
|
@parent = undefined
|
|
@select = (e) ->
|
|
|
|
addAction: (v) ->
|
|
v.parent = @
|
|
@child.push v
|
|
@
|
|
|
|
addActions: (list) ->
|
|
@addAction v for v in list
|
|
|
|
onchildselect: (f) ->
|
|
@select = f
|
|
@
|
|
|
|
run: (root) ->
|
|
root.openDialog(new CommandPalette(), @)
|
|
.then (d) =>
|
|
data = d.data.item.get("data")
|
|
return data.run root if data.run
|
|
@select d, root
|
|
|
|
CMDMenu.fromMenu = (mn) ->
|
|
m = new CMDMenu mn.text, mn.shortcut
|
|
m.onchildselect mn.onchildselect
|
|
for v in mn.child
|
|
if v.child
|
|
m.addAction CMDMenu.fromMenu v
|
|
else
|
|
m.addAction v
|
|
m
|
|
|
|
CodePad.CMDMenu = CMDMenu
|
|
|
|
CodePad.dependencies = [
|
|
"os://scripts/ace/ace.js",
|
|
"os://scripts/ace/ext-language_tools.js",
|
|
"os://scripts/ace/ext-modelist.js",
|
|
"os://scripts/ace/ext-themelist.js"
|
|
]
|
|
this.OS.register "CodePad", CodePad |