antosdk-apps/Antedit/build/debug/main.js
2021-04-20 17:36:01 +02:00

1780 lines
64 KiB
JavaScript

var OS;
(function (OS) {
let application;
(function (application) {
class BaseEditorModel {
/**
* Creates an instance of BaseEditorModel.
*
* @param {Antedit} app parent app
* @param {GUI.tag.TabBarTag} tabbar tabbar DOM element
* @param {HTMLElement} editorarea editor container DOM element
* @memberof BaseEditorModel
*/
constructor(app, tabbar, editorarea) {
this.container = editorarea;
this.currfile = "Untitled".asFileHandle();
this.tabbar = tabbar;
this.editorSetup(editorarea);
this.app = app;
this.editormux = false;
this.onstatuschange = undefined;
this.on("focus", () => {
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
});
this.on("input", () => {
if (this.editormux) {
this.editormux = false;
return false;
}
if (!this.currfile.dirty) {
this.currfile.dirty = true;
this.currfile.text += "*";
return this.tabbar.update(undefined);
}
});
this.on("changeCursor", () => {
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
});
this.tabbar.ontabselect = (e) => {
return this.selecteTab($(e.data.item).index());
};
this.tabbar.ontabclose = (e) => {
const it = e.data.item;
if (!it) {
return false;
}
if (!it.data.dirty) {
return this.closeTab(it);
}
this.app.openDialog("YesNoDialog", {
title: __("Close tab"),
text: __("Close without saving ?"),
}).then((d) => {
if (d) {
return this.closeTab(it);
}
return this.focus();
});
return false;
};
}
/**
* Find a tab on the tabbar corresponding to a file handle
*
* @private
* @param {EditorFileHandle} file then file handle to search
* @returns {number}
* @memberof BaseEditorModel
*/
findTabByFile(file) {
const lst = this.tabbar.items;
const its = (() => {
const result = [];
for (let i = 0; i < lst.length; i++) {
const d = lst[i];
if (d.hash() === file.hash()) {
result.push(i);
}
}
return result;
})();
if (its.length === 0) {
return -1;
}
return its[0];
}
/**
* Create new tab when opening a file
*
* @private
* @param {EditorFileHandle} file
* @memberof BaseEditorModel
*/
newTab(file) {
file.text = file.basename ? file.basename : file.path;
if (!file.cache) {
file.cache = "";
}
file.textModel = this.newTextModelFrom(file);
this.currfile.selected = false;
file.selected = true;
//console.log cnt
this.tabbar.push(file);
}
/**
* Close a tab when a file is closed
*
* @private
* @param {GUI.tag.ListViewItemTag} it reference to the tab to close
* @returns {boolean}
* @memberof BaseEditorModel
*/
closeTab(it) {
this.tabbar.delete(it);
const cnt = this.tabbar.items.length;
if (cnt === 0) {
this.openFile("Untitled".asFileHandle());
return false;
}
this.tabbar.selected = cnt - 1;
return false;
}
/**
* Select a tab by its index
*
* @private
* @param {number} i tab index
* @returns {void}
* @memberof BaseEditorModel
*/
selecteTab(i) {
//return if i is @tabbar.get "selidx"
const file = this.tabbar.items[i];
if (!file) {
return;
}
//return if file is @currfile
if (this.currfile !== file) {
this.currfile.textModel = this.getTexModel();
this.currfile.selected = false;
this.currfile = file;
}
this.editormux = true;
this.setTextModel(file.textModel);
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
this.focus();
}
/**
* Select an opened file, this will select the corresponding tab
*
* @param {(EditorFileHandle | string)} file
* @memberof BaseEditorModel
*/
selectFile(file) {
const i = this.findTabByFile(file.asFileHandle());
if (i !== -1) {
this.tabbar.selected = i;
}
}
/**
* Open a file in new tab. If the file is already opened,
* the just select the tab
*
*
* @param {EditorFileHandle} file file to open
* @returns {void}
* @memberof BaseEditorModel
*/
openFile(file) {
//find tab
const i = this.findTabByFile(file);
if (i !== -1) {
this.tabbar.selected = i;
return;
}
if (file.path.toString() === "Untitled") {
this.newTab(file);
return;
}
file.read()
.then((d) => {
file.cache = d || "";
return this.newTab(file);
})
.catch((e) => {
return this.app.error(__("Unable to open: {0}", file.path), e);
});
}
/**
* write a file
*
* @private
* @param {EditorFileHandle} file
* @memberof BaseEditorModel
*/
write(file) {
this.currfile.cache = this.getValue();
file.write("text/plain")
.then((d) => {
file.dirty = false;
file.text = file.basename;
this.tabbar.update(undefined);
})
.catch((e) => this.app.error(__("Unable to save file: {0}", file.path), e));
}
/**
* Save the current opened file
*
* @return {*} {void}
* @memberof BaseEditorModel
*/
save() {
this.currfile.cache = this.getValue();
if (this.currfile.basename) {
return this.write(this.currfile);
}
return this.saveAs();
}
/**
* Save the current file as another file
*
* @public
* @memberof BaseEditorModel
*/
saveAs() {
this.app.openDialog("FileDialog", {
title: __("Save as"),
file: this.currfile,
}).then((f) => {
let d = f.file.path.asFileHandle();
if (f.file.type === "file") {
d = d.parent();
}
this.currfile.setPath(`${d.path}/${f.name}`);
this.write(this.currfile);
});
}
/**
* Get all dirty file handles in the editor
*
* @return {*} {EditorFileHandle[]}
* @memberof BaseEditorModel
*/
dirties() {
const result = [];
for (let v of Array.from(this.tabbar.items)) {
if (v.dirty) {
result.push(v);
}
}
return result;
}
/**
* Context menu handle for the editor
*
* @memberof BaseEditorModel
*/
set contextmenuHandle(cb) {
this.container.contextmenuHandle = cb;
}
/**
* Close all opened files
*
* @memberof BaseEditorModel
*/
closeAll() {
this.tabbar.items = [];
this.resetEditor();
}
/**
* Check whether the editor is dirty
*
* @return {*} {boolean}
* @memberof BaseEditorModel
*/
isDirty() {
return this.dirties().length > 0;
}
}
application.BaseEditorModel = BaseEditorModel;
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
var OS;
(function (OS) {
let application;
(function (application) {
/**
* Wrapper model for the ACE text editor
*
* @export
* @class MonacoEditorModel
* @extends {BaseEditorModel}
*/
class MonacoEditorModel extends OS.application.BaseEditorModel {
/**
* Creates an instance of MonacoEditorModel.
* @param {MonacoEditorModel} app MonacoEditorModel instance
* @param {GUI.tag.TabBarTag} tabbar tabbar element
* @param {HTMLElement} editorarea main editor container element
* @memberof MonacoEditorModel
*/
constructor(app, tabbar, editorarea) {
super(app, tabbar, editorarea);
}
/**
* Reset the editor
*
* @protected
* @memberof MonacoEditorModel
*/
resetEditor() {
this.setValue("");
// TODO create new textmodel
}
/**
* Get a text model from the current editor session
*
* @protected
* @return {*}
* @memberof MonacoEditorModel
*/
getTexModel() {
return {
model: this.editor.getModel(),
position: this.editor.getPosition()
};
}
/**
* Set text model to current editor session
*
* @protected
* @param {*} model
* @memberof MonacoEditorModel
*/
setTextModel(model) {
this.editor.setModel(model.model);
if (model.position) {
this.editor.setPosition(model.position);
this.editor.revealLineInCenter(model.position.lineNumber);
}
}
/**
* Create new editor model from file
*
* @protected
* @param {EditorFileHandle} file
* @return {*} {*}
* @memberof MonacoEditorModel
*/
newTextModelFrom(file) {
if (file.path.toString() === "Untitled") {
return {
model: monaco.editor.createModel(file.cache, "textplain")
};
}
const uri = monaco.Uri.parse(file.path);
const model = monaco.editor.getModel(uri);
if (model) {
return { model: model };
}
return {
model: monaco.editor.createModel(file.cache, undefined, uri)
};
}
/**
* Get language modes
*
* @return {*} {GenericObject<any>[]}
* @memberof MonacoEditorModel
*/
getModes() {
//const list = [];
//return list;
return monaco.languages.getLanguages();
}
/**
* Set the editor theme
*
* @param {string} theme theme name
* @memberof MonacoEditorModel
*/
setTheme(theme) {
}
/**
* Set editor language mode
*
* The mode object should be in the following format:
* ```ts
* {
* text: string,
* mode: string
* }
* ```
*
* @param {GenericObject<any>} m language mode object
* @memberof MonacoEditorModel
*/
setMode(m) {
}
/**
* Setup the editor
*
* @protected
* @param {HTMLElement} el editor container DOM
* @memberof MonacoEditorModel
*/
editorSetup(el) {
this.editor = monaco.editor.create(el, {
value: "",
language: 'textplain'
});
if (!MonacoEditorModel.modes) {
MonacoEditorModel.modes = {};
monaco.languages.getLanguages().forEach((el) => {
MonacoEditorModel.modes[el.id] = el;
});
}
}
/**
* Register to editor event
*
* @param {string} evt_str event name
* @param {() => void} callback callback function
* @memberof MonacoEditorModel
*/
on(evt_str, callback) {
switch (evt_str) {
case "input":
this.editor.onDidChangeModelContent(callback);
break;
case "focus":
this.editor.onDidFocusEditorText(callback);
break;
case "changeCursor":
this.editor.onDidChangeCursorPosition(callback);
break;
default:
break;
}
}
/**
* Resize the editor
*
* @memberof MonacoEditorModel
*/
resize() {
if (this.editor)
this.editor.layout();
}
/**
* Focus on the editor
*
* @memberof MonacoEditorModel
*/
focus() {
if (this.editor)
this.editor.focus();
}
/**
* Get language mode from path
*
* @protected
* @param {string} path
* @return {*} {GenericObject<any>}
* @memberof MonacoEditorModel
*/
getModeForPath(path) {
return {};
}
/**
* Get the editor status
*
* @return {*} {GenericObject<any>}
* @memberof MonacoEditorModel
*/
getEditorStatus() {
const pos = this.editor.getPosition();
const mode = MonacoEditorModel.modes[this.editor.getModel().getModeId()];
return {
row: pos.lineNumber,
column: pos.column,
line: this.editor.getModel().getLineCount(),
langmode: {
text: mode.aliases[0],
mode: mode
},
file: this.currfile.path
};
}
/**
* Get editor value
*
* @return {*} {string}
* @memberof MonacoEditorModel
*/
getValue() {
return this.editor.getValue();
}
/**
* Set editor value
*
* @param {string} value
* @memberof MonacoEditorModel
*/
setValue(value) {
this.editor.setValue(value);
}
getEditor() {
return this.editor;
}
}
application.MonacoEditorModel = MonacoEditorModel;
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
__monaco_public_path__ = "VFS/get/" + "pkg://MonacoCore/bundle/".asFileHandle().path + "/";
var OS;
(function (OS) {
let application;
(function (application) {
/**
* A simple yet powerful code/text editor.
*
* Antedit is the default text editor shipped with
* AntOS base system. It is based on the Monaco editor
* which power the VS Code IDE.
*
* @export
* @class Antedit
* @extends {BaseApplication}
*/
class Antedit extends application.BaseApplication {
/**
*Creates an instance of Antedit.
* @param {AppArgumentsType[]} args application arguments
* @memberof Antedit
*/
constructor(args) {
super("Antedit", args);
this.currdir = undefined;
}
/**
* Main application entry point
*
* @returns {void}
* @memberof Antedit
*/
main() {
this.extensions = {};
this.eum = new EditorModelManager();
this.fileview = this.find("fileview");
this.sidebar = this.find("sidebar");
this.bottombar = this.find("bottombar");
this.langstat = this.find("langstat");
this.editorstat = this.find("editorstat");
this.filestat = this.find("current-file-lbl");
this.logger = new Logger(this.find("output-tab"));
this.split_mode = true;
this.fileview.fetch = (path) => new Promise(async function (resolve, reject) {
let dir;
if (typeof path === "string") {
dir = path.asFileHandle();
}
else {
dir = path;
}
try {
const d = await dir
.read();
if (d.error) {
return reject(d.error);
}
return resolve(d.result);
}
catch (e) {
return reject(__e(e));
}
});
let file = "Untitled".asFileHandle();
if (this.args && this.args.length > 0) {
this.addRecent(this.args[0].path);
if (this.args[0].type === "dir") {
this.currdir = this.args[0].path.asFileHandle();
}
else {
file = this.args[0].path.asFileHandle();
this.currdir = file.parent();
}
}
if (!this.setting.recent)
this.setting.recent = [];
const wrapper = this.find("wrapper");
$(wrapper).css('visibility', 'hidden');
monaco.editor.setTheme("vs-dark");
// add editor instance
this.eum
.add(new OS.application.MonacoEditorModel(this, this.find("left-tabbar"), this.find("left-editorarea")))
.add(new OS.application.MonacoEditorModel(this, this.find("right-tabbar"), this.find("right-editorarea")));
this.eum.onstatuschange = (st) => this.updateStatus(st);
$(wrapper).css('visibility', 'visible');
this.setup();
this.eum.active.openFile(file);
/*this.load(new Promise((resolve, reject) => {
require.config({ paths: { 'vs': "pkg://MonacoCore/vs".asFileHandle().getlink() }});
require(['vs/editor/editor.main'], () => {
resolve(undefined);
});
}))*/
}
/**
* Set up the text editor
*
* @private
* @returns {void}
* @memberof Antedit
*/
setup() {
this.fileview.onfileopen = (e) => {
if (!e.data || !e.data.path) {
return;
}
if (e.data.type === "dir") {
return;
}
this.addRecent(e.data.path);
return this.eum.active.openFile(e.data.path.asFileHandle());
};
this.fileview.onfileselect = (e) => {
if (!e.data || !e.data.path) {
return;
}
if (e.data.type === "dir") {
return;
}
this.eum.active.selectFile(e.data.path);
};
this.on("resize", () => this.eum.resize());
this.on("focus", () => this.eum.active.focus());
this.fileview.contextmenuHandle = (e, m) => {
m.items = [
{ text: "__(New file)", id: "new" },
{ text: "__(New folder)", id: "newdir" },
{ text: "__(Rename)", id: "rename" },
{ text: "__(Delete)", id: "delete" },
];
m.onmenuselect = (e) => {
return this.ctxFileMenuHandle(e);
};
return m.show(e);
};
this.bindKey("ALT-N", () => this.menuAction("new"));
this.bindKey("ALT-O", () => this.menuAction("open"));
this.bindKey("ALT-F", () => this.menuAction("opendir"));
this.bindKey("CTRL-S", () => this.menuAction("save"));
this.bindKey("ALT-W", () => this.menuAction("saveas"));
this.fileview.ondragndrop = (e) => {
const src = e.data.from.data.path.asFileHandle();
const des = e.data.to.data.path;
return src
.move(`${des}/${src.basename}`)
.then(function (d) {
const p1 = des;
const p2 = src.parent().path;
if (p1.length < p2.length) {
e.data.to.update(p1);
e.data
.from.parent.update(p2);
}
else {
e.data
.from.parent.update(p2);
e.data.to.update(p1);
}
})
.catch((e) => this.error(__("Unable to move file/folder"), e));
};
this.on("filechange", (data) => {
let { path } = data.file;
if (data.type === "file") {
({ path } = data.file.parent());
}
return this.fileview.update(path);
});
this.find("logger-clear").onbtclick = () => {
this.logger.clear();
};
if (this.setting.showBottomBar === undefined) {
this.setting.showBottomBar = false;
}
this.loadExtensionMetaData();
this.toggleSideBar();
this.toggleSplitMode();
this.applyAllSetting();
}
/**
* Update the editor status bar
*
* @private
* @memberof Antedit
*/
updateStatus(stat = undefined) {
if (!stat)
stat = this.eum.active.getEditorStatus();
this.editorstat.text = __("Row {0}, col {1}, lines: {2}", stat.row, stat.column, stat.line);
if (stat.langmode)
this.langstat.text = stat.langmode.text;
this.filestat.text = stat.file;
let win = this.scheme;
if (win.apptitle != stat.file)
win.apptitle = stat.file;
}
/**
* Show or hide the SideBar
*
* @memberof Antedit
*/
toggleSideBar() {
if (this.currdir) {
$(this.sidebar).show();
this.fileview.path = this.currdir.path;
}
else {
$(this.sidebar).hide();
}
this.trigger("resize");
}
showOutput(toggle = false) {
if (toggle)
this.showBottomBar(true);
this.bottombar.selectedIndex = 0;
}
/**
* Apply [[showBottomBar]] from user setting value
*
* @protected
* @param {string} k
* @memberof Antedit
*/
applySetting(k) {
if (k == "showBottomBar") {
this.showBottomBar(this.setting.showBottomBar);
}
}
/**
* Show or hide the bottom bar and
* save the value to user setting
*
* @param {boolean} v
* @memberof Antedit
*/
showBottomBar(v) {
this.setting.showBottomBar = v;
if (v) {
$(this.bottombar).show();
}
else {
$(this.bottombar).hide();
}
this.trigger("resize");
}
/**
* toggle the bottom bar
*
* @memberof Antedit
*/
toggleBottomBar() {
this.showBottomBar(!this.setting.showBottomBar);
}
toggleSplitMode() {
const right_pannel = this.find("right-panel");
const right_editor = this.eum.editors[1];
const left_editor = this.eum.editors[0];
if (this.split_mode) {
// before hide check if there is dirty files
if (right_editor.isDirty()) {
this.notify(__("Unable to disable split view: Please save changes of modified files on the right panel"));
return;
}
right_editor.closeAll();
$(right_pannel).hide();
this.split_mode = false;
left_editor.focus();
}
else {
$(right_pannel).show();
this.split_mode = true;
right_editor.openFile("Untitled".asFileHandle());
right_editor.focus();
}
this.trigger("resize");
}
/**
* File menu definition
*
* @private
* @returns {GUI.BasicItemType}
* @memberof Antedit
*/
fileMenu() {
const recent = this.setting.recent.map((i) => {
return { text: i };
});
return {
text: __("File"),
nodes: [
{ text: __("New"), dataid: "new", shortcut: "A-N" },
{
text: __("Open Recent"),
dataid: "recent",
nodes: recent,
onchildselect: (e, r) => {
const handle = e.data.item.data.text.asFileHandle();
handle.onready().then((meta) => {
if (!meta) {
return;
}
if (meta.type == "dir") {
this.currdir = handle;
this.toggleSideBar();
}
else {
this.eum.active.openFile(handle);
}
});
}
},
{ 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) => {
return this.menuAction(e.data.item.data.dataid, r);
},
};
}
/**
* Context menu definition
*
* @private
* @param {GUI.TagEventType} e
* @returns {void}
* @memberof Antedit
*/
ctxFileMenuHandle(e) {
const el = e.data.item;
if (!el) {
return;
}
const data = el.data;
if (!data) {
return;
}
let file = this
.fileview.selectedFile;
let dir = this.currdir;
if (file && file.type === "dir") {
dir = file.path.asFileHandle();
}
if (file && file.type === "file") {
dir = file.path.asFileHandle().parent();
}
switch (data.id) {
case "new":
if (!dir) {
return;
}
this.openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)",
}).then(async (d) => {
const fp = `${dir.path}/${d}`.asFileHandle();
try {
const r = await fp.write("text/plain");
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to create: {0}", e.stack), e);
}
});
break;
case "newdir":
if (!dir) {
return;
}
this.openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)",
}).then(async (d) => {
try {
const r = await dir.mk(d);
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to create: {0}", dir.path), e);
}
});
break;
case "rename":
if (!file) {
return;
}
this.openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename,
}).then(async (d) => {
if (d === file.filename) {
return;
}
file = file.path.asFileHandle();
dir = file.parent();
try {
const r = await file.move(`${dir.path}/${d}`);
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to rename: {0}", file.path), e);
}
});
break;
case "delete":
if (!file) {
return;
}
this.openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename),
}).then(async (d) => {
if (!d) {
return;
}
file = file.path.asFileHandle();
dir = file.parent();
try {
const r = await file.remove();
return this.fileview.update(dir.path);
}
catch (e) {
return this.error(__("Fail to delete: {0}", file.path), e);
}
});
break;
default:
}
}
/**
* Add a file to recent files setting
*
* @private
* @param {string} file
* @memberof Antedit
*/
addRecent(file) {
if (!this.setting.recent)
this.setting.recent = [];
if (this.setting.recent.includes(file)) {
return;
}
this.setting.recent.push(file);
if (this.setting.recent.length > 10)
this.setting.recent = this.setting.recent.slice(0, 10);
}
/**
* Menu action definition
*
* @private
* @param {string} dataid
* @param {Antedit} [r]
* @returns {void}
* @memberof Antedit
*/
menuAction(dataid, r) {
let me = this;
if (r) {
me = r;
}
switch (dataid) {
case "new":
return me.eum.active.openFile("Untitled".asFileHandle());
case "open":
return me
.openDialog("FileDialog", {
title: __("Open file"),
mimes: Array.from(me.meta().mimes).filter((v) => v !== "dir"),
})
.then((f) => {
this.addRecent(f.file.path);
me.eum.active.openFile(f.file.path.asFileHandle());
});
case "opendir":
return me
.openDialog("FileDialog", {
title: __("Open folder"),
mimes: ["dir"],
})
.then(function (f) {
me.addRecent(f.file.path);
me.currdir = f.file.path.asFileHandle();
return me.toggleSideBar();
});
case "save":
return me.eum.active.save();
case "saveas":
return me.eum.active.saveAs();
default:
return console.log(dataid);
}
}
/**
* Cleanup the editor before exiting.
*
* @param {BaseEvent} evt
* @returns {void}
* @memberof Antedit
*/
cleanup(evt) {
let v;
const dirties = this.eum.dirties();
if (dirties.length === 0) {
// cleanup all extension
for (let k in this.extensions) {
if (this.extensions[k] && this.extensions[k].cleanup) {
this.extensions[k].cleanup();
}
}
return;
}
evt.preventDefault();
this.openDialog("YesNoDialog", {
title: "__(Quit)",
text: __("Ignore all unsaved files: {0} ?", (() => {
const result1 = [];
for (v of Array.from(dirties)) {
result1.push(v.filename);
}
return result1;
})().join(", ")),
}).then((d) => {
if (d) {
for (v of Array.from(dirties)) {
v.dirty = false;
}
return this.quit(false);
}
});
}
/**
* Application menu definition
*
* @returns {GUI.BasicItemType[]}
* @memberof Antedit
*/
menu() {
return [
this.fileMenu(),
{
text: "__(View)",
nodes: [
{
text: "__(Toggle bottom bar)",
dataid: "bottombar"
},
{
text: "__(Toggle split view)",
dataid: "splitview"
}
],
onchildselect: (e, r) => {
switch (e.data.item.data.dataid) {
case "bottombar":
return this.toggleBottomBar();
case "splitview":
return this.toggleSplitMode();
break;
default:
break;
}
r;
},
},
];
}
/**
* Load the extension meta data from `extension.json` file
*
* @memberof AntEdit
*/
loadExtensionMetaData() {
this.loadExtensionMetaFromFile(`${this.meta().path}/extensions/extensions.json`)
.catch((e) => {
return this.error(__("Cannot load extension meta data"), e);
});
}
/**
* Load extension meta-data from specific file
*
* @private
* @param {string} path
* @return {*} {Promise<void>}
* @memberof AntEdit
*/
loadExtensionMetaFromFile(path) {
return new Promise((resolve, reject) => {
path
.asFileHandle()
.read("json")
.then((d) => {
for (let extension of d) {
for (let act of extension.actions) {
this.eum.addAction(extension, act, (e_name, a_name) => {
this.loadAndRunExtensionAction(e_name, a_name, extension.root);
});
}
}
resolve();
})
.catch((e) => {
reject(__e(e));
});
});
}
/**
* Load extension then run an action
*
* @param name extension name
* @param action action name
* @memberof AntEdit
*/
loadAndRunExtensionAction(name, action, root) {
//verify if the extension is load
if (!Antedit.extensions[name]) {
//load the extension
let path = `${this.meta().path}/extensions/${name}/main.js`;
if (root)
path = `${root}/main.js`;
this._api
.requires(path, true)
.then(() => this.runExtensionAction(name, action))
.catch((e) => {
return this.error(__("unable to load extension: {0}", name), e);
});
}
else {
this.runExtensionAction(name, action);
}
}
/**
* Run an extension action from the command palette
*
* @private
* @param {string} name extension name
* @param {string} action action name
* @returns {void}
* @memberof AntEdit
*/
runExtensionAction(name, action) {
if (!this.extensions[name]) {
if (!Antedit.extensions[name]) {
return this.error(__("Unable to find extension: {0}", name));
}
this.extensions[name] = new Antedit.extensions[name](this);
}
if (!this.extensions[name][action]) {
return this.error(__("Unable to find action: {0}", action));
}
this.extensions[name].preload()
.then(() => this.extensions[name][action]())
.catch((e) => {
return this.error(__("Unable to preload extension"), e);
});
}
}
application.Antedit = Antedit;
/**
* Helper class to manager several instances
* of editor models
*
* @class EditorModelManager
*/
class EditorModelManager {
/**
* Creates an instance of EditorModelManager.
* @memberof EditorModelManager
*/
constructor() {
this.active_editor = undefined;
this.models = [];
}
get editors() {
return this.models;
}
set contextmenuHandle(cb) {
for (let ed of this.models) {
ed.contextmenuHandle = cb;
}
}
/**
* Get the active editor model
*
* @readonly
* @type {BaseEditorModel}
* @memberof EditorModelManager
*/
get active() {
return this.active_editor;
}
/**
* Add a model to the manager
*
* @param {BaseEditorModel} model
* @memberof EditorModelManager
*/
add(model) {
this.models.push(model);
if (!this.active_editor)
this.active_editor = model;
model.on("focus", () => {
this.active_editor = model;
});
return this;
}
addAction(extension, action, callback) {
const ed_action = {
id: `${extension.name}:${action.name}`,
label: `${extension.text.__()}: ${action.text.__()}`,
/*
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10,
// chord
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_K, monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_M)
]*/
precondition: null,
keybindingContext: null,
contextMenuGroupId: extension.name,
//contextMenuOrder: 1.5,
run: () => callback(extension.name, action.name)
};
for (let ed of this.models) {
const editor = ed.getEditor();
if (!editor.getAction(ed_action.id))
editor.addAction(ed_action);
}
}
set onstatuschange(cb) {
for (let ed of this.models) {
ed.onstatuschange = cb;
}
}
dirties() {
let list = [];
for (let ed of this.models) {
list = list.concat(ed.dirties());
}
return list;
}
/**
* Resize all editor
*
* @memberof EditorModelManager
*/
resize() {
for (let ed of this.models) {
ed.resize();
}
}
}
/**
* This class handles log output to the Editor output container
*
* @class Logger
*/
class Logger {
/**
* Creates an instance of Logger.
* @param {HTMLElement} el target container
* @memberof Logger
*/
constructor(el) {
this.target = el;
}
/**
* Log level info
*
* @param {string|FormattedString} s
* @memberof Logger
*/
info(s) {
this.log("info", s, true);
}
/**
* Log level warning
*
* @param {string|FormattedString} s
* @memberof Logger
*/
warn(s) {
this.log("warn", s, true);
}
/**
* Log level error
*
* @param {string|FormattedString} s
* @memberof Logger
*/
error(s) {
this.log("error", s, true);
}
/**
* Log a string to target container
*
* @private
* @param {string} c class name of the appended log element
* @param {string|FormattedString} s log string
* @param {boolean} showtime define whether the logger should insert datetime prefix
* in the log string
* @memberof Logger
*/
log(c, s, showtime) {
let el = $("<pre></pre>")
.attr("class", `code-pad-log-${c}`);
if (showtime) {
let date = new Date();
let prefix = date.getDate() + "/"
+ (date.getMonth() + 1) + "/"
+ date.getFullYear() + " "
+ date.getHours() + ":"
+ date.getMinutes() + ":"
+ date.getSeconds();
el.text(`[${prefix}]: ${s.__()}`);
}
else {
el.text(s.__());
}
$(this.target).append(el);
$(this.target).scrollTop($(this.target)[0].scrollHeight);
}
/**
* Print a log message without prefix
*
* @param {string|FormattedString} s text to print
* @memberof Logger
*/
print(s) {
this.log("info", s, false);
}
/**
* Empty the log container
*
* @memberof Logger
*/
clear() {
$(this.target).empty();
}
}
Antedit.Logger = Logger;
Antedit.dependencies = [
"pkg://MonacoCore/bundle/app.bundle.js"
];
})(application = OS.application || (OS.application = {}));
})(OS || (OS = {}));
var OS;
(function (OS) {
/**
*
*
* @class EditorBaseExtension
*/
class EditorBaseExtension {
constructor(name, app) {
this.app = app;
this.name = name;
}
/**
*
*
* @returns {Promise<any>}
* @memberof EditorBaseExtension
*/
preload() {
return OS.API.require(OS.application.Antedit.extensions[this.name].dependencies);
}
/**
*
*
* @protected
* @returns {string}
* @memberof EditorBaseExtension
*/
basedir() {
return `${this.app.meta().path}/extensions/${this.name}`;
}
/**
*
*
* @protected
* @param {(string | FormattedString)} m
* @returns {void}
* @memberof EditorBaseExtension
*/
notify(m) {
return this.app.notify(m);
}
/**
*
*
* @protected
* @param {(string | FormattedString)} m
* @param {Error} e
* @returns {void}
* @memberof EditorBaseExtension
*/
error(m, e) {
return this.app.error(m, e);
}
/**
*
*
* @protected
* @return {AnteditLogger} editor logger
* @memberof EditorBaseExtension
*/
logger() {
if (!this.app.setting.showBottomBar) {
this.app.showOutput(true);
}
else {
this.app.showOutput(false);
}
return this.app.logger;
}
/**
*
*
* @protected
* @param {string} file
* @returns {Promise<GenericObject<any>>}
* @memberof EditorBaseExtension
*/
metadata(file) {
return new Promise((resolve, reject) => {
if (!this.app.currdir) {
return reject(OS.API.throwe(__("Current folder is not found")));
}
`${this.app.currdir.path}/${file}`
.asFileHandle()
.read("json")
.then((data) => {
if (!data.root && this.app.currdir) {
data.root = this.app.currdir.path;
}
resolve(data);
})
.catch((e) => {
// try to ask user to select a folder
this.app.openDialog("FileDialog", {
title: __("Select build directory"),
root: this.app.currdir.path,
mimes: ["dir"]
})
.then((d) => {
`${d.file.path}/${file}`
.asFileHandle()
.read("json")
.then((data) => {
if (!data.root) {
data.root = d.file.path;
}
resolve(data);
})
.catch((e1) => reject(e1));
})
.catch((e1) => reject(OS.API.throwe(__("Unable to read meta-data"))));
});
});
}
}
EditorBaseExtension.dependencies = [];
OS.application.Antedit.extensions = {};
OS.application.Antedit.EditorBaseExtension = EditorBaseExtension;
class EditorExtensionMaker extends EditorBaseExtension {
constructor(app) {
super("EditorExtensionMaker", app);
}
create() {
this.logger().clear();
this.app
.openDialog("FileDialog", {
title: "__(New CodePad extension at)",
file: { basename: __("ExtensionName") },
mimes: ["dir"],
})
.then((d) => {
return this.mktpl(d.file.path, d.name);
});
}
build(callback) {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
try {
const jsrc = await OS.API.VFS.cat(meta.javascripts.map(v => `${meta.root}/${v}`), "");
await `${meta.root}/build/debug/main.js`
.asFileHandle()
.setCache(jsrc)
.write("text/plain");
await `${meta.root}/build/debug/extension.json`
.asFileHandle()
.setCache(meta.meta)
.write("object");
await OS.API.VFS.copy(meta.copies.map(v => `${meta.root}/${v}`), `${meta.root}/build/debug`);
this.logger().info(__("Files generated in {0}", `${meta.root}/build/debug`));
if (callback)
callback();
}
catch (e) {
return this.logger().error(__("Unable to build extension:{0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack)));
}
run() {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
if (!meta || !meta.meta || !meta.meta.name)
return this.logger().error(__("Invalid extension meta-data"));
try {
const path = `${meta.root}/build/debug/main.js`;
if (OS.API.shared[path]) {
delete OS.API.shared[path];
}
await OS.API.requires(path);
if (this.app.extensions[meta.meta.name] && this.app.extensions[meta.meta.name].cleanup) {
this.app.extensions[meta.meta.name].cleanup();
}
this.app.extensions[meta.meta.name] = new OS.application.Antedit.extensions[meta.meta.name](this.app);
for (let v of meta.meta.actions) {
this.app.eum.addAction(meta.meta, v, (e_name, a_name) => {
this.app.loadAndRunExtensionAction(e_name, a_name, `${meta.root}/build`);
});
}
this.app.eum.active.getEditor().trigger(meta.meta.name, 'editor.action.quickCommand');
}
catch (e) {
return this.logger().error(__("Unable to run extension:{0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack)));
}
release() {
this.logger().clear();
this.metadata("extension.json")
.then(async (meta) => {
this.build(async () => {
try {
await OS.API.VFS.mkar(`${meta.root}/build/debug`, `${meta.root}/build/release/${meta.meta.name}.zip`);
this.logger().info(__("Archive created at {0}", `${meta.root}/build/release/${meta.meta.name}.zip`));
}
catch (e) {
return this.logger().error(__("Unable to create archive: {0}", e.stack));
}
});
})
.catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack)));
}
install() {
this.logger().clear();
this.app
.openDialog("FileDialog", {
title: "__(Select extension archive)",
mimes: [".*/zip"],
})
.then(async (d) => {
try {
await this.installZip(d.file.path);
this.logger().info(__("Extension installed"));
return this.app.loadExtensionMetaData();
}
catch (e) {
return this.logger().error(__("Unable to install extension: {0}", e.stack));
}
});
}
installFromURL() {
this.logger().clear();
this.app
.openDialog("PromptDialog", {
title: __("Enter URI"),
label: __("Please enter extension URI:")
})
.then(async (v) => {
if (!v)
return;
try {
await this.installZip(v);
this.logger().info(__("Extension installed"));
return this.app.loadExtensionMetaData();
}
catch (e) {
return this.app.error(__("Unable to install extension: {0}", v));
}
});
}
/**
*
*
* @private
* @param {string} path
* @param {string} name
* @memberof EditorExtensionMaker
*/
mktpl(path, name) {
const rpath = `${path}/${name}`;
const dirs = [
rpath,
`${rpath}/build`,
`${rpath}/build/release`,
`${rpath}/build/debug`,
];
const files = [
["main.tpl", `${rpath}/${name}.js`],
["meta.tpl", `${rpath}/extension.json`],
];
OS.API.VFS.mkdirAll(dirs, true)
.then(async () => {
try {
await OS.API.VFS.mktpl(files, this.basedir(), (data) => {
return data.format(name, `${path}/${name}`);
});
this.app.currdir = rpath.asFileHandle();
this.app.toggleSideBar();
return this.app.eum.active.openFile(`${rpath}/${name}.js`.asFileHandle());
}
catch (e) {
return this.logger().error(__("Unable to create extension template: {0}", e.stack));
}
})
.catch((e) => this.logger().error(__("Unable to create extension directories: {0}", e.stack)));
}
/**
*
*
* @private
* @param {string} path
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
installZip(path) {
return new Promise(async (resolve, reject) => {
try {
await OS.API.requires("os://scripts/jszip.min.js");
const data = await path.asFileHandle().read("binary");
const zip = await JSZip.loadAsync(data);
const d = await zip.file("extension.json").async("uint8array");
const meta = JSON.parse(new TextDecoder("utf-8").decode(d));
const pth = this.ext_dir(meta.name);
const dir = [pth];
const files = [];
for (let name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dir.push(pth + "/" + name);
}
else if (name != "extension.json") {
files.push(name);
}
}
if (dir.length > 0) {
await OS.API.VFS.mkdirAll(dir, true);
await this.installFiles(files, zip, meta);
}
else {
await this.installFiles(files, zip, meta);
}
resolve();
}
catch (e) {
reject(__e(e));
}
});
}
ext_dir(en) {
return `${this.app.meta().path}/extensions/${en}`;
}
/**
*
*
* @private
* @param {string[]} files
* @param {*} zip
* @param {GenericObject<any>} meta
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
installFiles(files, zip, meta) {
if (files.length === 0) {
return this.installMeta(meta);
}
return new Promise(async (resolve, reject) => {
try {
const file = files.splice(0, 1)[0];
const path = `${this.ext_dir(meta.name)}/${file}`;
const d = await zip.file(file).async("uint8array");
const r = await path.asFileHandle()
.setCache(new Blob([d], { type: "octet/stream" }))
.write("text/plain");
if (r.error) {
return reject(r.error);
}
await this.installFiles(files, zip, meta);
resolve();
}
catch (e) {
reject(__e(e));
}
});
}
/**
*
*
* @private
* @param {GenericObject<any>} meta
* @returns {Promise<void>}
* @memberof EditorExtensionMaker
*/
installMeta(meta) {
return new Promise(async (resolve, reject) => {
const file = `${this.ext_dir("")}/extensions.json`.asFileHandle();
try {
const data = await file.read("json");
const names = [];
for (let v of data) {
names.push(v.name);
}
const idx = names.indexOf(meta.name);
if (idx >= 0) {
data.splice(idx, 1);
}
data.push(meta);
try {
await file.setCache(data).write("object");
return resolve();
}
catch (e) {
return reject(__e(e));
}
}
catch (e_1) {
// try to create new file
try {
await file.setCache([meta]).write("object");
return resolve();
}
catch (e_2) {
return reject(__e(e_2));
}
}
});
}
}
OS.application.Antedit.extensions.EditorExtensionMaker = EditorExtensionMaker;
})(OS || (OS = {}));