From 5f0588ee27bb23303f1c751c7a3f817bb0d632d7 Mon Sep 17 00:00:00 2001 From: lxsang Date: Mon, 4 Jul 2022 19:31:17 +0200 Subject: [PATCH] Add gitgrap app & library --- Antedit/build/debug/main.js | 2337 +++++++++++++++++++++++++- GitGrapth/LibGitGraph.ts | 645 +++++++ GitGrapth/README.md | 15 + GitGrapth/api/api.lua | 86 + GitGrapth/build.json | 99 ++ GitGrapth/build/debug/README.md | 15 + GitGrapth/build/debug/api/api.lua | 86 + GitGrapth/build/debug/libgitgraph.js | 498 ++++++ GitGrapth/build/debug/main.css | 201 +++ GitGrapth/build/debug/main.js | 538 ++++++ GitGrapth/build/debug/package.json | 16 + GitGrapth/build/debug/scheme.html | 8 + GitGrapth/main.css | 201 +++ GitGrapth/main.ts | 44 + GitGrapth/package.json | 16 + GitGrapth/scheme.html | 8 + 16 files changed, 4812 insertions(+), 1 deletion(-) create mode 100644 GitGrapth/LibGitGraph.ts create mode 100644 GitGrapth/README.md create mode 100644 GitGrapth/api/api.lua create mode 100644 GitGrapth/build.json create mode 100644 GitGrapth/build/debug/README.md create mode 100644 GitGrapth/build/debug/api/api.lua create mode 100644 GitGrapth/build/debug/libgitgraph.js create mode 100644 GitGrapth/build/debug/main.css create mode 100644 GitGrapth/build/debug/main.js create mode 100644 GitGrapth/build/debug/package.json create mode 100644 GitGrapth/build/debug/scheme.html create mode 100644 GitGrapth/main.css create mode 100644 GitGrapth/main.ts create mode 100644 GitGrapth/package.json create mode 100644 GitGrapth/scheme.html diff --git a/Antedit/build/debug/main.js b/Antedit/build/debug/main.js index 0ad3073..80e2f39 100644 --- a/Antedit/build/debug/main.js +++ b/Antedit/build/debug/main.js @@ -1 +1,2336 @@ -var OS;!function(t){let e,i;!function(e){let i;!function(e){class i extends t.API.VFS.BaseFileHandle{constructor(t){super(""),this.path=`${t[0].path} -> ${t[1].path}`,this.cache=t,this.basename=`${t[0].basename} -> ${t[1].basename}`,this.info={type:"file",mime:void 0,size:0,name:this.basename,path:this.path},this.ready=!0}meta(){return new Promise(async(t,e)=>{try{await Promise.all([this.cache[0].meta(),this.cache[1].meta]),t({result:this.info,error:!1})}catch(t){e(t)}})}_rd(t){return new Promise(async(t,e)=>{try{this.cache[0].cache=await this.cache[0].read(),this.cache[1].cache=await this.cache[1].read(),t(this.cache)}catch(t){e(t)}})}_wr(t,e){return this.cache=e,new Promise((t,e)=>{t({result:!0,error:!1})})}setPath(t){}}e.DiffEditorFileHandle=i}(i=e.VFS||(e.VFS={}))}(e=t.API||(t.API={})),function(t){t.BaseEditorModel=class{constructor(t,e,i){this.container=i,this.currfile="Untitled".asFileHandle(),this.tabbar=e,this.editorSetup(i),this.app=t,this.onstatuschange=void 0,this.on("focus",()=>{this.onstatuschange&&this.onstatuschange(this.getEditorStatus())}),this.on("input",()=>{if(!this.currfile.dirty)return console.log("dirty",this.currfile.path),this.currfile.dirty=!0,this.currfile.text+="*",this.tabbar.update(void 0)}),this.on("changeCursor",()=>{this.onstatuschange&&this.onstatuschange(this.getEditorStatus())}),this.tabbar.ontabselect=t=>this.selecteTab($(t.data.item).index()),this.tabbar.ontabclose=t=>{const e=t.data.item;return!!e&&(e.data.dirty?(this.app.openDialog("YesNoDialog",{title:__("Close tab"),text:__("Close without saving ?")}).then(t=>t?this.closeTab(e):this.focus()),!1):this.closeTab(e))}}findTabByFile(t){const e=this.tabbar.items,i=(()=>{const i=[];for(let a=0;a(t.cache=e||"",this.newTab(t))).catch(e=>this.app.error(__("Unable to open: {0}",t.path),e)):this.newTab(t):this.tabbar.selected=e}write(){this.currfile.cache=this.getValue(),this.currfile.write("text/plain").then(t=>{this.currfile.dirty=!1,this.currfile.text=this.currfile.basename,this.tabbar.update(void 0)}).catch(t=>this.app.error(__("Unable to save file: {0}",this.currfile.path),t))}save(){return this.currfile.cache=this.getValue(),this.currfile.basename?this.write():this.saveAs()}saveAs(){this.app.openDialog("FileDialog",{title:__("Save as"),file:this.currfile}).then(t=>{let e=t.file.path.asFileHandle();"file"===t.file.type&&(e=e.parent()),this.currfile.setPath(`${e.path}/${t.name}`),this.write()})}dirties(){const t=[];for(let e of Array.from(this.tabbar.items))e.dirty&&t.push(e);return t}set contextmenuHandle(t){this.container.contextmenuHandle=t}closeAll(){this.tabbar.items=[],this.openFile("Untitled".asFileHandle()),this.resetEditor()}isDirty(){return this.dirties().length>0}setTabbarCtxMenu(t,e){this.tabbar.contextmenuHandle=(i,a)=>(a.items=t,a.onmenuselect=t=>{if(e){const a=$(i.target).closest("afx-list-item");e(a[0],t.data.item.data)}},a.show(i))}}}(i=t.application||(t.application={}))}(OS||(OS={})),function(t){let e;!function(e){class i extends t.application.BaseEditorModel{constructor(t,e,i){super(t,e,i)}resetEditor(){}getTexModel(){return{model:this.editor.getModel(),position:this.editor.getPosition()}}setTextModel(t){this.editor.setModel(t.model),t.position&&(this.editor.setPosition(t.position),this.editor.revealLineInCenter(t.position.lineNumber))}newTextModelFrom(t){if(Array.isArray(t.cache))return{model:{original:this.newTextModelFrom(t.cache[0]).model,modified:this.newTextModelFrom(t.cache[1]).model}};{if("Untitled"===t.path.toString())return{model:monaco.editor.createModel(t.cache,"textplain")};const e=monaco.Uri.parse(t.protocol+"://antedit/file/"+t.genealogy.join("/")),i=monaco.editor.getModel(e);return i?(i.setValue(t.cache),{model:i}):{model:monaco.editor.createModel(t.cache,void 0,e)}}}getModes(){return monaco.languages.getLanguages().map(t=>{const e=t;return t.aliases?e.text=t.aliases[0]:e.text=t.id,t})}setTheme(t){}setMode(t){if(this.editor==this._code_editor)monaco.editor.setModelLanguage(this.editor.getModel(),t.id);else for(const e of this.editor.getModel())monaco.editor.setModelLanguage(e,t.id);this.onstatuschange&&this.onstatuschange(this.getEditorStatus())}get editor(){return Array.isArray(this.currfile.cache)?this._diff_editor:this._code_editor}editorSetup(t){this.code_container=$("
").css("width","100%").css("height","100%"),this.diff_container=$("
").css("width","100%").css("height","100%").css("display","none"),$(t).append(this.code_container),$(t).append(this.diff_container),this._code_editor=monaco.editor.create(this.code_container[0],{value:"",language:"textplain"}),this._diff_editor=monaco.editor.createDiffEditor(this.diff_container[0],{readOnly:!0}),i.modes||(i.modes={},monaco.languages.getLanguages().forEach(t=>{i.modes[t.id]=t}))}on(t,e){switch(t){case"input":this._code_editor.onDidChangeModelContent(e);break;case"focus":this._code_editor.onDidFocusEditorText(e),this._diff_editor.getOriginalEditor().onDidFocusEditorText(e),this._diff_editor.getModifiedEditor().onDidFocusEditorText(e);break;case"changeCursor":this._code_editor.onDidChangeCursorPosition(e),this._diff_editor.getOriginalEditor().onDidChangeCursorPosition(e),this._diff_editor.getModifiedEditor().onDidChangeCursorPosition(e)}}resize(){this.editor&&this.editor.layout()}focus(){Array.isArray(this.currfile.cache)?(this.code_container.hide(),this.diff_container.show()):(this.code_container.show(),this.diff_container.hide()),this.editor&&(this.editor.layout(),this.editor.focus())}getModeForPath(t){return{}}getEditorStatus(){let t=void 0;this.editor==this._code_editor?t=this.editor:(t=this.editor.getOriginalEditor(),this.editor.getModifiedEditor().hasTextFocus()&&(t=this.editor.getModifiedEditor()));const e=t.getPosition();let a=void 0;const s=t.getModel();return s&&(a=i.modes[s.getLanguageId()]),{row:e.lineNumber,column:e.column,line:s?s.getLineCount():0,langmode:{text:a?a.aliases[0]:"",mode:a},file:this.currfile.path}}getValue(){return this.editor==this._code_editor?this.editor.getValue():this.currfile.cache}setValue(t){this.editor==this._code_editor&&this.editor.setValue(t)}getEditor(){return this.editor}}e.MonacoEditorModel=i}(e=t.application||(t.application={}))}(OS||(OS={})),function(t){let e,i;!function(t){let e;!function(t){class e extends t.ListViewItemTag{itemlayout(){return{el:"div",children:[{el:"afx-label",ref:"label"},{el:"p",ref:"desc",id:"ext-list-item-d-p"},{el:"p",id:"ext-list-item-b-p",children:[{el:"i",ref:"intall_status"},{el:"afx-button",ref:"btn_remove"},{el:"afx-button",ref:"btn_install"}]}]}}ondatachange(){const t=this.data;if(!t)return;const e=this.refs.label;e.iconclass="bi bi-puzzle",e.text=`${t.text} - v${t.version}`;const i=this.refs.desc;$(i).text(t.description);const a=this.refs.btn_install,s=this.refs.btn_remove;t.installed?($(s).show(),s.iconclass="bi bi-trash-fill",a.iconclass="bi bi-arrow-repeat",$(this.refs.intall_status).text(__("Installed: v{0} ",t.installed).__())):($(s).hide(),a.iconclass="fa bi-cloud-download-fill",$(this.refs.intall_status).text(" "))}init(){this.closable=!1,this.data={};const t=this.refs.btn_install,e=this.refs.btn_remove;t.onbtclick=t=>{this.data.download&&this.data.install_action&&this.data.install_action(this.data.download,t=>{this.data.installed=t,this.update(void 0)})},e.onbtclick=t=>{this.data.installed&&this.data.uninstall_action&&this.data.uninstall_action(this.data.name,()=>{delete this.data.installed,this.update(void 0)})}}reload(t){this.data=this.data}}t.define("afx-antedit-ext-list-item",e)}(e=t.tag||(t.tag={}))}(e=t.GUI||(t.GUI={})),function(e){class i extends e.BaseApplication{constructor(t){super("Antedit",t),this.currdir=void 0,this.diff_buffer=[void 0,void 0]}main(){this.extensions={},this.eum=new a,this.fileview=this.find("fileview"),this.sidebar=this.find("sidebar"),this.sidebar_container=this.find("sidebar-tab-container"),this.bottombar=this.find("bottombar"),this.langstat=this.find("langstat"),this.editorstat=this.find("editorstat"),this.filestat=this.find("current-file-lbl"),this.extension_list_view=this.find("extension-list"),this.logger=new s(this.find("output-tab")),this.split_mode=!0,this.fileview.fetch=t=>new Promise((async function(e,i){let a;a="string"==typeof t?t.asFileHandle():t;try{const t=await a.read();return t.error?i(t.error):e(t.result)}catch(t){return i(__e(t))}}));let e="Untitled".asFileHandle();this.args&&this.args.length>0&&(this.addRecent(this.args[0].path),"dir"===this.args[0].type?this.currdir=this.args[0].path.asFileHandle():(e=this.args[0].path.asFileHandle(),this.currdir=e.parent())),this.setting.recent||(this.setting.recent=[]),this.setting.extension_repos||(this.setting.extension_repos=["https://raw.githubusercontent.com/lxsang/antos-antedit-extensions/master/extensions.json"]);const i=this.find("wrapper");$(i).css("visibility","hidden"),monaco.editor.setTheme("vs-dark");const n=new t.application.MonacoEditorModel(this,this.find("left-tabbar"),this.find("left-editorarea")),o=new t.application.MonacoEditorModel(this,this.find("right-tabbar"),this.find("right-editorarea"));n.setTabbarCtxMenu(this.tb_ctxmenu,(t,e)=>this.tabbar_ctx_menu_handle(t,e,n)),o.setTabbarCtxMenu(this.tb_ctxmenu,(t,e)=>this.tabbar_ctx_menu_handle(t,e,o)),this.eum.add(n).add(o),this.eum.onstatuschange=t=>this.updateStatus(t),$(i).css("visibility","visible"),this.setup(),this.eum.active.openFile(e)}get tb_ctxmenu(){return[{text:"__(Close)",id:"close"},{text:"__(Close All)",id:"close-all"},{text:"__(Move to other side)",id:"mv-side"}]}tabbar_ctx_menu_handle(t,e,i){switch(e.id){case"close":if(!t)return;i.closeTab(t);break;case"close-all":i.closeAll();break;case"mv-side":if(!t)return;let e=this.eum.editors[0];i==e&&(e=this.eum.editors[1]),e.openFile(t.data),i.closeTab(t),0==this.split_mode&&this.toggleSplitMode()}}setup(){this.sidebar_container.selectedIndex=0,this.extension_list_view.itemtag="afx-antedit-ext-list-item",this.fileview.onfileopen=t=>{if(t.data&&t.data.path&&(this.addRecent(t.data.path),"dir"!==t.data.type))return this.eum.active.openFile(t.data.path.asFileHandle())},this.fileview.onfileselect=t=>{t.data&&t.data.path&&"dir"!==t.data.type&&this.eum.active.selectFile(t.data.path)},this.on("resize",()=>this.eum.resize()),this.on("focus",()=>this.eum.active.focus()),this.fileview.contextmenuHandle=(t,e)=>{let i=this.fileview.selectedFile;const a=[{text:"__(New file)",id:"new"},{text:"__(New folder)",id:"newdir"},{text:"__(Rename)",id:"rename"},{text:"__(Delete)",id:"delete"},{text:"__(Upload)",id:"upload"}];return i&&"file"===i.type&&(a.push({text:"__(Select for compare)",id:"diff-org"}),a.push({text:"__(Compare with selected)",id:"diff-mod"})),e.items=a,e.onmenuselect=t=>this.ctxFileMenuHandle(t),e.show(t)},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=t=>{if(!t.data.from||!t.data.to)return;const e=t.data.from[0].data.path.asFileHandle(),i=t.data.to.data.path;return e.move(`${i}/${e.basename}`).then((function(a){const s=i,n=e.parent().path;s.lengththis.error(__("Unable to move file/folder"),t))},this.on("filechange",t=>{let{path:e}=t.file;return"file"===t.type&&({path:e}=t.file.parent()),this.fileview.update(e)}),this.find("logger-clear").onbtclick=()=>{this.logger.clear()},void 0===this.setting.showBottomBar&&(this.setting.showBottomBar=!1);const t={name:"Editor",text:__("Editor")},e={name:"langmode",text:__("Change language mode"),shortcut:"CTRL-K"};this.eum.addAction(t,e,async t=>{try{const t=await this.openDialog("SelectionDialog",{title:__("Select language"),data:this.eum.active.getModes()});this.eum.active.setMode(t)}catch(t){console.log(t)}}),$(this.find("txt_ext_search")).keyup(t=>this.extension_search(t)),this.loadExtensionMetaData(),this.toggleSideBar(),this.toggleSplitMode(),this.applyAllSetting()}extension_search(t){let e;const i=this.find("txt_ext_search");switch(t.which){case 37:return t.preventDefault();case 38:return this.extension_list_view.selectPrev(),t.preventDefault();case 39:return t.preventDefault();case 40:return this.extension_list_view.selectNext(),t.preventDefault();case 13:return t.preventDefault();default:var a=i.value,s=[];if(2===a.length)return void(this.extension_list_view.data=this.extension_meta_data);if(a.length<3)return;var n=new RegExp(a,"i");for(e in this.extension_meta_data)this.extension_meta_data[e].text.match(n)&&s.push(this.extension_meta_data[e]);this.extension_list_view.data=s}}refreshExtensionRepositories(){const t=[],e=this.meta().path+"/extensions/extensions.json";for(let i of[e].concat(this.setting.extension_repos))t.push(i.asFileHandle().read("json"));Promise.all(t).then(t=>{const e={};for(let i of t.shift())e[i.name]=i;this.extension_meta_data=[];for(let a of t)for(let t of a)e[t.name]&&(t.installed=e[t.name].version),t.install_action=(e,a)=>{new i.extensions.EditorExtensionMaker(this).installZip(e).then(()=>{this.loadExtensionMetaData(),a&&a(t.version),this.notify(__("Extension '{0}' installed",t.text))}).catch(e=>{this.error(__("Unable to install '{0}': {1}",t.text,e.toString()),e)})},t.uninstall_action=(t,e)=>{new i.extensions.EditorExtensionMaker(this).uninstall(t).then(()=>{this.loadExtensionMetaData(),e&&e(),this.notify(__("Extension '{0}' uninstalled",t))}).catch(e=>{this.error(__("Unable to uninstall '{0}': {1}",t,e.toString()),e)})},this.extension_meta_data.push(t);this.extension_list_view.data=this.extension_meta_data}).catch(t=>{this.error(__("Unable to read extension from repositories: {0}",t.toString()),t)})}updateStatus(t){t||(t=this.eum.active.getEditorStatus()),this.editorstat.text=__("Row {0}, col {1}, lines: {2}",t.row,t.column,t.line),t.langmode&&(this.langstat.text=t.langmode.text),this.filestat.text=t.file;let e=this.scheme;e.apptitle!=t.file&&(e.apptitle=t.file)}toggleSideBar(){this.currdir?($(this.sidebar).show(),this.fileview.path=this.currdir.path,this.refreshExtensionRepositories()):$(this.sidebar).hide(),this.trigger("resize")}showOutput(t=!1){t&&this.showBottomBar(!0),this.bottombar.selectedIndex=0}openDiff(e){const i=new t.API.VFS.DiffEditorFileHandle(e);this.eum.active.openFile(i)}applySetting(t){"showBottomBar"==t&&this.showBottomBar(this.setting.showBottomBar)}showBottomBar(t){this.setting.showBottomBar=t,t?$(this.bottombar).show():$(this.bottombar).hide(),this.trigger("resize")}toggleBottomBar(){this.showBottomBar(!this.setting.showBottomBar)}toggleSplitMode(){const t=this.find("right-panel"),e=this.eum.editors[1],i=this.eum.editors[0];if(this.split_mode){if(e.isDirty())return void this.notify(__("Unable to disable split view: Please save changes of modified files on the right panel"));e.closeAll(),$(t).hide(),this.split_mode=!1,i.focus()}else $(t).show(),this.split_mode=!0,e.openFile("Untitled".asFileHandle()),e.focus();this.trigger("resize")}fileMenu(){const t=this.setting.recent.map(t=>({text:t}));return{text:__("File"),nodes:[{text:__("New"),dataid:"new",shortcut:"A-N"},{text:__("Open Recent"),dataid:"recent",nodes:t,onchildselect:(t,e)=>{const i=t.data.item.data.text.asFileHandle();i.onready().then(t=>{t&&("dir"==t.type?(this.currdir=i,this.toggleSideBar()):this.eum.active.openFile(i))})}},{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:(t,e)=>this.menuAction(t.data.item.data.dataid,e)}}ctxFileMenuHandle(t){const e=t.data.item;if(!e)return;const i=e.data;if(!i)return;let a=this.fileview.selectedFile,s=this.currdir;switch(a&&"dir"===a.type&&(s=a.path.asFileHandle()),a&&"file"===a.type&&(s=a.path.asFileHandle().parent()),i.id){case"new":if(!s)return;this.openDialog("PromptDialog",{title:"__(New file)",label:"__(File name)"}).then(async t=>{const e=`${s.path}/${t}`.asFileHandle();try{return await e.write("text/plain"),this.fileview.update(s.path)}catch(t){return this.error(__("Fail to create: {0}",t.stack),__e(t))}});break;case"newdir":if(!s)return;this.openDialog("PromptDialog",{title:"__(New folder)",label:"__(Folder name)"}).then(async t=>{try{return await s.mk(t),this.fileview.update(s.path)}catch(t){return this.error(__("Fail to create: {0}",s.path),__e(t))}});break;case"rename":if(!a)return;this.openDialog("PromptDialog",{title:"__(Rename)",label:"__(File name)",value:a.filename}).then(async t=>{if(t!==a.filename){a=a.path.asFileHandle(),s=a.parent();try{return await a.move(`${s.path}/${t}`),this.fileview.update(s.path)}catch(t){return this.error(__("Fail to rename: {0}",a.path),__e(t))}}});break;case"delete":if(!a)return;this.openDialog("YesNoDialog",{title:"__(Delete)",iconclass:"fa fa-question-circle",text:__("Do you really want to delete: {0}?",a.filename)}).then(async t=>{if(t){a=a.path.asFileHandle(),s=a.parent();try{return await a.remove(),this.fileview.update(s.path)}catch(t){return this.error(__("Fail to delete: {0}",a.path),__e(t))}}});break;case"upload":if(!s)return;s.upload().then(t=>(this.notify(__("File uploaded to: {0}",s.path)),this.fileview.update(s.path))).catch(t=>this.error(__("Unable to upload file: {e}",t.toString()),__e(t)));break;case"diff-org":if(!a)return;this.diff_buffer[0]=a.path.asFileHandle();break;case"diff-mod":if(!a)return;if(!this.diff_buffer[0])return;this.diff_buffer[1]=a.path.asFileHandle(),this.openDiff(this.diff_buffer)}}addRecent(t){this.setting.recent||(this.setting.recent=[]),this.setting.recent.includes(t)||(this.setting.recent.unshift(t),this.setting.recent.length>10&&(this.setting.recent=this.setting.recent.slice(0,10)))}menuAction(t,e){let i=this;switch(e&&(i=e),t){case"new":return i.eum.active.openFile("Untitled".asFileHandle());case"open":return i.openDialog("FileDialog",{title:__("Open file"),mimes:Array.from(i.meta().mimes).filter(t=>"dir"!==t)}).then(t=>{this.addRecent(t.file.path),i.eum.active.openFile(t.file.path.asFileHandle())});case"opendir":return i.openDialog("FileDialog",{title:__("Open folder"),mimes:["dir"]}).then((function(t){return i.addRecent(t.file.path),i.currdir=t.file.path.asFileHandle(),i.toggleSideBar()}));case"save":return i.eum.active.save();case"saveas":return i.eum.active.saveAs();default:return console.log(t)}}cleanup(t){let e;const i=this.eum.dirties();if(0!==i.length)t.preventDefault(),this.openDialog("YesNoDialog",{title:"__(Quit)",text:__("Ignore all unsaved files: {0} ?",(()=>{const t=[];for(e of Array.from(i))t.push(e.filename);return t})().join(", "))}).then(t=>{if(t){for(e of Array.from(i))e.dirty=!1;return this.quit(!1)}});else for(let t in this.extensions)this.extensions[t]&&this.extensions[t].cleanup&&this.extensions[t].cleanup()}menu(){return[this.fileMenu(),{text:"__(View)",nodes:[{text:"__(Toggle bottom bar)",dataid:"bottombar"},{text:"__(Toggle split view)",dataid:"splitview"}],onchildselect:(t,e)=>{switch(t.data.item.data.dataid){case"bottombar":return this.toggleBottomBar();case"splitview":return this.toggleSplitMode()}}}]}loadExtensionMetaData(){this.loadExtensionMetaFromFile(this.meta().path+"/extensions/extensions.json").catch(t=>this.error(__("Cannot load extension meta data"),t))}loadExtensionMetaFromFile(t){return new Promise((e,i)=>{t.asFileHandle().read("json").then(t=>{for(let e of t)for(let t of e.actions)this.eum.addAction(e,t,(t,i)=>{this.loadAndRunExtensionAction(t,i,e.root)});e()}).catch(t=>{i(__e(t))})})}loadAndRunExtensionAction(t,e,a){if(i.extensions[t])this.runExtensionAction(t,e);else{let i=`${this.meta().path}/extensions/${t}/main.js`;a&&(i=a+"/main.js"),this._api.requires(i,!0).then(()=>this.runExtensionAction(t,e)).catch(e=>this.error(__("unable to load extension: {0}",t),e))}}runExtensionAction(t,e){if(!this.extensions[t]){if(!i.extensions[t])return this.error(__("Unable to find extension: {0}",t));this.extensions[t]=new i.extensions[t](this)}if(!this.extensions[t][e])return this.error(__("Unable to find action: {0}",e));this.extensions[t].preload().then(()=>this.extensions[t][e]()).catch(t=>this.error(__("Unable to preload extension"),t))}}e.Antedit=i;class a{constructor(){this.active_editor=void 0,this.models=[]}get editors(){return this.models}set contextmenuHandle(t){for(let e of this.models)e.contextmenuHandle=t}get active(){return this.active_editor}add(t){return this.models.push(t),this.active_editor||(this.active_editor=t),t.on("focus",()=>{this.active_editor=t}),this}addAction(t,e,i){const a={id:`${t.name}:${e.name}`,label:`${t.text.__()}: ${e.text.__()}`,keybindings:[],precondition:null,keybindingContext:null,contextMenuGroupId:t.name,run:()=>i(t.name,e.name)};if(e.shortcut){const t=e.shortcut.split("-");let i=0;for(const e of t)switch(e){case"CTRL":i|=monaco.KeyMod.CtrlCmd;break;case"ALT":i|=monaco.KeyMod.Alt;break;case"SHIFT":i|=monaco.KeyMod.Shift;break;case"SUPPER":i|=monaco.KeyMod.WinCtrl;break;default:const t="Key"+e;monaco.KeyCode[t]?i|=monaco.KeyCode[t]:i=0}0!=i&&a.keybindings.push(i)}for(let t of this.models){const e=t.getEditor();e.getAction(a.id)||e.addAction(a)}}set onstatuschange(t){for(let e of this.models)e.onstatuschange=t}dirties(){let t=[];for(let e of this.models)t=t.concat(e.dirties());return t}resize(){for(let t of this.models)t.resize()}}class s{constructor(t){this.target=t}info(t){this.log("info",t,!0)}warn(t){this.log("warn",t,!0)}error(t){this.log("error",t,!0)}log(t,e,i){let a=$("
").attr("class","code-pad-log-"+t);if(i){let t=new Date,i=t.getDate()+"/"+(t.getMonth()+1)+"/"+t.getFullYear()+" "+t.getHours()+":"+t.getMinutes()+":"+t.getSeconds();a.text(`[${i}]: ${e.__()}`)}else a.text(e.__());$(this.target).append(a),$(this.target).scrollTop($(this.target)[0].scrollHeight)}print(t){t.match(/warn/i)?this.log("warn",t,!1):t.match(/error/i)?this.log("error",t,!1):this.log("info",t,!1)}clear(){$(this.target).empty()}}i.Logger=s,i.dependencies=["pkg://MonacoCore/path.js","pkg://MonacoCore/bundle/app.bundle.js"]}(i=t.application||(t.application={}))}(OS||(OS={})),function(t){class e{constructor(t,e){this.app=e,this.name=t}preload(){return t.API.require(t.application.Antedit.extensions[this.name].dependencies)}basedir(){return`${this.app.meta().path}/extensions/${this.name}`}notify(t){return this.app.notify(t)}error(t,e){return this.app.error(t,e)}logger(){return this.app.setting.showBottomBar?this.app.showOutput(!1):this.app.showOutput(!0),this.app.logger}metadata(e){return new Promise((i,a)=>{if(!this.app.currdir)return a(t.API.throwe(__("Current folder is not found")));`${this.app.currdir.path}/${e}`.asFileHandle().read("json").then(t=>{!t.root&&this.app.currdir&&(t.root=this.app.currdir.path),i(t)}).catch(s=>{this.app.openDialog("FileDialog",{title:__("Select build directory"),root:this.app.currdir.path,mimes:["dir"]}).then(t=>{`${t.file.path}/${e}`.asFileHandle().read("json").then(e=>{e.root||(e.root=t.file.path),i(e)}).catch(t=>a(t))}).catch(e=>a(t.API.throwe(__("Unable to read meta-data"))))})})}}e.dependencies=[],t.application.Antedit.extensions={},t.application.Antedit.EditorBaseExtension=e,t.application.Antedit.extensions.EditorExtensionMaker=class extends e{constructor(t){super("EditorExtensionMaker",t)}create(){this.logger().clear(),this.app.openDialog("FileDialog",{title:"__(New extension at)",file:{basename:__("ExtensionName")},mimes:["dir"]}).then(t=>this.mktpl(t.file.path,t.name))}build(e){this.logger().clear(),this.metadata("extension.json").then(async i=>{try{const a=await t.API.VFS.cat(i.javascripts.map(t=>`${i.root}/${t}`),"");await(i.root+"/build/debug/main.js").asFileHandle().setCache(a).write("text/plain"),await(i.root+"/build/debug/extension.json").asFileHandle().setCache(i.meta).write("object"),await t.API.VFS.copy(i.copies.map(t=>`${i.root}/${t}`),i.root+"/build/debug"),this.logger().info(__("Files generated in {0}",i.root+"/build/debug")),e&&e()}catch(t){return this.logger().error(__("Unable to build extension:{0}",t.stack))}}).catch(t=>this.logger().error(__("Unable to read meta-data:{0}",t.stack)))}run(){this.logger().clear(),this.metadata("extension.json").then(async e=>{if(!e||!e.meta||!e.meta.name)return this.logger().error(__("Invalid extension meta-data"));try{const i=e.root+"/build/debug/main.js";t.API.shared[i]&&delete t.API.shared[i],await t.API.requires(i),this.app.extensions[e.meta.name]&&this.app.extensions[e.meta.name].cleanup&&this.app.extensions[e.meta.name].cleanup(),this.app.extensions[e.meta.name]=new t.application.Antedit.extensions[e.meta.name](this.app);for(let t of e.meta.actions)this.app.eum.addAction(e.meta,t,(t,i)=>{this.app.loadAndRunExtensionAction(t,i,e.root+"/build")});this.app.eum.active.getEditor().trigger(e.meta.name,"editor.action.quickCommand")}catch(t){return this.logger().error(__("Unable to run extension:{0}",t.stack))}}).catch(t=>this.logger().error(__("Unable to read meta-data:{0}",t.stack)))}release(){this.logger().clear(),this.metadata("extension.json").then(async e=>{this.build(async()=>{try{await t.API.VFS.mkar(e.root+"/build/debug",`${e.root}/build/release/${e.meta.name}.zip`),this.logger().info(__("Archive created at {0}",`${e.root}/build/release/${e.meta.name}.zip`))}catch(t){return this.logger().error(__("Unable to create archive: {0}",t.stack))}})}).catch(t=>this.logger().error(__("Unable to read meta-data: {0}",t.stack)))}install(){this.logger().clear(),this.app.openDialog("FileDialog",{title:"__(Select extension archive)",mimes:[".*/zip"]}).then(async t=>{try{return await this.installZip(t.file.path),this.logger().info(__("Extension installed")),this.app.loadExtensionMetaData()}catch(t){return this.logger().error(__("Unable to install extension: {0}",t.stack))}})}installFromURL(){this.logger().clear(),this.app.openDialog("PromptDialog",{title:__("Enter URI"),label:__("Please enter extension URI:")}).then(async t=>{if(t)try{return await this.installZip(t),this.logger().info(__("Extension installed")),this.app.loadExtensionMetaData()}catch(e){return this.app.error(__("Unable to install extension: {0}",t))}})}mktpl(e,i){const a=`${e}/${i}`,s=[a,a+"/build",a+"/build/release",a+"/build/debug"],n=[["main.tpl",`${a}/${i}.js`],["meta.tpl",a+"/extension.json"]];t.API.VFS.mkdirAll(s,!0).then(async()=>{try{return await t.API.VFS.mktpl(n,this.basedir(),t=>t.format(i,`${e}/${i}`)),this.app.currdir=a.asFileHandle(),this.app.toggleSideBar(),this.app.eum.active.openFile(`${a}/${i}.js`.asFileHandle())}catch(t){return this.logger().error(__("Unable to create extension template: {0}",t.stack))}}).catch(t=>this.logger().error(__("Unable to create extension directories: {0}",t.stack)))}uninstall(t){return new Promise(async(e,i)=>{try{const i=this.app.meta().path+"/extensions",a=(i+"/extensions.json").asFileHandle(),s=await a.read("json");let n=void 0,o=void 0;for(let e in s)if(s[e].name===t){n=s[e],o=e;break}if(void 0===n)return e();await`${i}/${t}`.asFileHandle().remove(),s.splice(o,1),a.cache=s,await a.write("object"),e()}catch(t){i(t)}})}installZip(e){return new Promise(async(i,a)=>{try{await t.API.requires("os://scripts/jszip.min.js");const a=await e.asFileHandle().read("binary"),s=await JSZip.loadAsync(a),n=await s.file("extension.json").async("uint8array"),o=JSON.parse(new TextDecoder("utf-8").decode(n));await this.uninstall(o.name);const r=this.ext_dir(o.name),l=[r],h=[];for(let t in s.files)s.files[t].dir?l.push(r+"/"+t):"extension.json"!=t&&h.push(t);l.length>0?(await t.API.VFS.mkdirAll(l,!0),await this.installFiles(h,s,o)):await this.installFiles(h,s,o),i()}catch(t){a(__e(t))}})}ext_dir(t){return`${this.app.meta().path}/extensions/${t}`}installFiles(t,e,i){return 0===t.length?this.installMeta(i):new Promise(async(a,s)=>{try{const n=t.splice(0,1)[0],o=`${this.ext_dir(i.name)}/${n}`,r=await e.file(n).async("uint8array"),l=await o.asFileHandle().setCache(new Blob([r],{type:"octet/stream"})).write("text/plain");if(l.error)return s(l.error);await this.installFiles(t,e,i),a()}catch(t){s(__e(t))}})}installMeta(t){return new Promise(async(e,i)=>{const a=(this.ext_dir("")+"/extensions.json").asFileHandle();try{const s=await a.read("json"),n=[];for(let t of s)n.push(t.name);const o=n.indexOf(t.name);o>=0&&s.splice(o,1),s.push(t);try{return await a.setCache(s).write("object"),e()}catch(t){return i(__e(t))}}catch(s){try{return await a.setCache([t]).write("object"),e()}catch(t){return i(__e(t))}}})}}}(OS||(OS={}));
\ No newline at end of file
+
+var OS;
+(function (OS) {
+    let API;
+    (function (API) {
+        let VFS;
+        (function (VFS) {
+            class DiffEditorFileHandle extends OS.API.VFS.BaseFileHandle {
+                constructor(files) {
+                    super("");
+                    this.path = `${files[0].path} -> ${files[1].path}`;
+                    this.cache = files;
+                    this.basename = `${files[0].basename} -> ${files[1].basename}`;
+                    this.info = {
+                        type: "file",
+                        mime: undefined,
+                        size: 0,
+                        name: this.basename,
+                        path: this.path
+                    };
+                    this.ready = true;
+                }
+                meta() {
+                    return new Promise(async (resolve, reject) => {
+                        try {
+                            await Promise.all([this.cache[0].meta(), this.cache[1].meta]);
+                            resolve({
+                                result: this.info,
+                                error: false,
+                            });
+                        }
+                        catch (e) {
+                            reject(e);
+                        }
+                    });
+                }
+                _rd(_t) {
+                    return new Promise(async (resolve, reject) => {
+                        try {
+                            this.cache[0].cache = await this.cache[0].read();
+                            this.cache[1].cache = await this.cache[1].read();
+                            resolve(this.cache);
+                        }
+                        catch (e) {
+                            reject(e);
+                        }
+                    });
+                }
+                _wr(t, d) {
+                    this.cache = d;
+                    return new Promise((resolve, reject) => {
+                        resolve({
+                            result: true,
+                            error: false,
+                        });
+                    });
+                }
+                setPath(s) {
+                    // do nothing
+                }
+            }
+            VFS.DiffEditorFileHandle = DiffEditorFileHandle;
+        })(VFS = API.VFS || (API.VFS = {}));
+    })(API = OS.API || (OS.API = {}));
+    let application;
+    (function (application) {
+        class BaseEditorModel {
+            /**
+             * Editor mutex
+             *
+             * @private
+             * @type {boolean}
+             * @memberof BaseEditorModel
+             */
+            //private editormux: boolean;
+            /**
+             * 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", () => {
+                    // console.log(this.editormux, this.currfile.dirty);
+                    /*if (this.editormux) {
+                        this.editormux = false;
+                        console.log("set editor mux to false");
+                        return false;
+                    }*/
+                    if (!this.currfile.dirty) {
+                        console.log("dirty", this.currfile.path);
+                        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
+             *
+             * @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);
+                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() {
+                this.currfile.cache = this.getValue();
+                this.currfile.write("text/plain")
+                    .then((d) => {
+                    this.currfile.dirty = false;
+                    this.currfile.text = this.currfile.basename;
+                    this.tabbar.update(undefined);
+                })
+                    .catch((e) => this.app.error(__("Unable to save file: {0}", this.currfile.path), e));
+            }
+            /**
+             * Save the current opened file
+             *
+             * @return {*}  {void}
+             * @memberof BaseEditorModel
+             */
+            save() {
+                this.currfile.cache = this.getValue();
+                if (this.currfile.basename) {
+                    return this.write();
+                }
+                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();
+                });
+            }
+            /**
+             * 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.openFile("Untitled".asFileHandle());
+                this.resetEditor();
+            }
+            /**
+             * Check whether the editor is dirty
+             *
+             * @return {*}  {boolean}
+             * @memberof BaseEditorModel
+             */
+            isDirty() {
+                return this.dirties().length > 0;
+            }
+            /**
+             * Set model tabbar menu context event
+             *
+             * @memberof BaseEditorModel
+             */
+            setTabbarCtxMenu(items, handle) {
+                this.tabbar.contextmenuHandle = (evt, m) => {
+                    m.items = items;
+                    m.onmenuselect = (e) => {
+                        if (handle) {
+                            /**
+                             * get the tab under the cursor
+                             */
+                            const tab = $(evt.target).closest("afx-list-item");
+                            handle(tab[0], e.data.item.data);
+                        }
+                    };
+                    return m.show(evt);
+                };
+            }
+        }
+        application.BaseEditorModel = BaseEditorModel;
+    })(application = OS.application || (OS.application = {}));
+})(OS || (OS = {}));
+
+var OS;
+(function (OS) {
+    //declare var monaco: any;
+    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() {
+            }
+            /**
+             * 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 (Array.isArray(file.cache)) {
+                    return {
+                        model: {
+                            original: this.newTextModelFrom(file.cache[0]).model,
+                            modified: this.newTextModelFrom(file.cache[1]).model
+                        }
+                    };
+                }
+                else {
+                    if (file.path.toString() === "Untitled") {
+                        return {
+                            model: monaco.editor.createModel(file.cache, "textplain")
+                        };
+                    }
+                    const uri = monaco.Uri.parse(file.protocol + "://antedit/file/" + file.genealogy.join("/"));
+                    const model = monaco.editor.getModel(uri);
+                    if (model) {
+                        model.setValue(file.cache);
+                        return { model: model };
+                    }
+                    return {
+                        model: monaco.editor.createModel(file.cache, undefined, uri)
+                    };
+                }
+            }
+            /**
+             * Get language modes
+             *
+             * @return {*}  {GenericObject[]}
+             * @memberof MonacoEditorModel
+             */
+            getModes() {
+                //const list = [];
+                //return list;
+                return monaco.languages.getLanguages().map(e => {
+                    const item = e;
+                    if (e.aliases)
+                        item.text = e.aliases[0];
+                    else
+                        item.text = e.id;
+                    return e;
+                });
+            }
+            /**
+             * 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} m language mode object
+             * @memberof MonacoEditorModel
+             */
+            setMode(m) {
+                if (this.editor == this._code_editor) {
+                    monaco.editor.setModelLanguage(this.editor.getModel(), m.id);
+                }
+                else {
+                    for (const model of this.editor.getModel()) {
+                        monaco.editor.setModelLanguage(model, m.id);
+                    }
+                }
+                if (this.onstatuschange)
+                    this.onstatuschange(this.getEditorStatus());
+            }
+            /**
+             * Getter get current editor instance based on current file
+             *
+             * @private
+             * @type {GenericObject}
+             * @memberof MonacoEditorModel
+             */
+            get editor() {
+                if (Array.isArray(this.currfile.cache)) {
+                    return this._diff_editor;
+                }
+                return this._code_editor;
+            }
+            /**
+             * Setup the editor
+             *
+             * @protected
+             * @param {HTMLElement} el editor container DOM
+             * @memberof MonacoEditorModel
+             */
+            editorSetup(el) {
+                // create two editor instancs for code mode and diff mode
+                this.code_container = $("
") + .css("width", "100%") + .css("height", "100%"); + this.diff_container = $("
") + .css("width", "100%") + .css("height", "100%") + .css("display", "none"); + $(el).append(this.code_container); + $(el).append(this.diff_container); + this._code_editor = monaco.editor.create(this.code_container[0], { + value: "", + language: 'textplain' + }); + this._diff_editor = monaco.editor.createDiffEditor(this.diff_container[0], { + readOnly: true + }); + 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._code_editor.onDidChangeModelContent(callback); + break; + case "focus": + this._code_editor.onDidFocusEditorText(callback); + this._diff_editor.getOriginalEditor().onDidFocusEditorText(callback); + this._diff_editor.getModifiedEditor().onDidFocusEditorText(callback); + break; + case "changeCursor": + this._code_editor.onDidChangeCursorPosition(callback); + this._diff_editor.getOriginalEditor().onDidChangeCursorPosition(callback); + this._diff_editor.getModifiedEditor().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 (Array.isArray(this.currfile.cache)) { + this.code_container.hide(); + this.diff_container.show(); + } + else { + this.code_container.show(); + this.diff_container.hide(); + } + if (this.editor) { + this.editor.layout(); + this.editor.focus(); + } + } + /** + * Get language mode from path + * + * @protected + * @param {string} path + * @return {*} {GenericObject} + * @memberof MonacoEditorModel + */ + getModeForPath(path) { + return {}; + } + /** + * Get the editor status + * + * @return {*} {GenericObject} + * @memberof MonacoEditorModel + */ + getEditorStatus() { + let ed = undefined; + if (this.editor == this._code_editor) { + ed = this.editor; + } + else { + ed = this.editor.getOriginalEditor(); + if (this.editor.getModifiedEditor().hasTextFocus()) { + ed = this.editor.getModifiedEditor(); + } + } + const pos = ed.getPosition(); + let mode = undefined; + const model = ed.getModel(); + if (model) { + mode = MonacoEditorModel.modes[model.getLanguageId()]; + } + return { + row: pos.lineNumber, + column: pos.column, + line: model ? model.getLineCount() : 0, + langmode: { + text: mode ? mode.aliases[0] : "", + mode: mode + }, + file: this.currfile.path + }; + } + /** + * Get editor value + * + * @return {*} {string} + * @memberof MonacoEditorModel + */ + getValue() { + if (this.editor == this._code_editor) + return this.editor.getValue(); + return this.currfile.cache; + } + /** + * Set editor value + * + * @param {string} value + * @memberof MonacoEditorModel + */ + setValue(value) { + if (this.editor == this._code_editor) + this.editor.setValue(value); + } + getEditor() { + return this.editor; + } + } + application.MonacoEditorModel = MonacoEditorModel; + })(application = OS.application || (OS.application = {})); +})(OS || (OS = {})); + +var OS; +(function (OS) { + let GUI; + (function (GUI) { + let tag; + (function (tag) { + class AntEditExtensionListItem extends tag.ListViewItemTag { + itemlayout() { + return { + el: "div", + children: [ + { el: "afx-label", ref: "label" }, + { el: "p", ref: "desc", id: "ext-list-item-d-p" }, + { + el: "p", + id: "ext-list-item-b-p", + children: [ + { + el: "i", + ref: "intall_status" + }, + { + el: "afx-button", + ref: "btn_remove" + }, + { + el: "afx-button", + ref: "btn_install" + } + ] + } + ] + }; + } + ondatachange() { + const v = this.data; + if (!v) { + return; + } + const label = this.refs.label; + label.iconclass = "bi bi-puzzle"; + label.text = `${v.text} - v${v.version}`; + // add description + const p_desc = this.refs.desc; + $(p_desc).text(v.description); + // button install + const btn_install = this.refs.btn_install; + // button remove + const btn_remove = this.refs.btn_remove; + if (v.installed) { + $(btn_remove).show(); + btn_remove.iconclass = "bi bi-trash-fill"; + btn_install.iconclass = "bi bi-arrow-repeat"; + $(this.refs.intall_status).text(__("Installed: v{0} ", v.installed).__()); + } + else { + $(btn_remove).hide(); + btn_install.iconclass = "fa bi-cloud-download-fill"; + $(this.refs.intall_status).text(" "); + } + } + init() { + this.closable = false; + this.data = {}; + // button install + const btn_install = this.refs.btn_install; + // button remove + const btn_remove = this.refs.btn_remove; + btn_install.onbtclick = (e) => { + if (!this.data.download || !this.data.install_action) { + return; + } + this.data.install_action(this.data.download, (v) => { + this.data.installed = v; + this.update(undefined); + }); + }; + btn_remove.onbtclick = (e) => { + if (!this.data.installed || !this.data.uninstall_action) { + return; + } + this.data.uninstall_action(this.data.name, () => { + delete this.data.installed; + this.update(undefined); + }); + }; + } + reload(d) { + this.data = this.data; + } + } + tag.define("afx-antedit-ext-list-item", AntEditExtensionListItem); + })(tag = GUI.tag || (GUI.tag = {})); + })(GUI = OS.GUI || (OS.GUI = {})); + let application; + (function (application) { + const DEFAULT_REPO = "https://raw.githubusercontent.com/lxsang/antos-antedit-extensions/master/extensions.json"; + /** + * 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; + this.diff_buffer = [undefined, 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.sidebar_container = this.find("sidebar-tab-container"); + this.bottombar = this.find("bottombar"); + this.langstat = this.find("langstat"); + this.editorstat = this.find("editorstat"); + this.filestat = this.find("current-file-lbl"); + this.extension_list_view = this.find("extension-list"); + 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 = []; + if (!this.setting.extension_repos) + this.setting.extension_repos = [DEFAULT_REPO]; + const wrapper = this.find("wrapper"); + $(wrapper).css('visibility', 'hidden'); + monaco.editor.setTheme("vs-dark"); + // add editor instance + const left_editor = new OS.application.MonacoEditorModel(this, this.find("left-tabbar"), this.find("left-editorarea")); + const right_editor = new OS.application.MonacoEditorModel(this, this.find("right-tabbar"), this.find("right-editorarea")); + left_editor.setTabbarCtxMenu(this.tb_ctxmenu, (tab, data) => this.tabbar_ctx_menu_handle(tab, data, left_editor)); + right_editor.setTabbarCtxMenu(this.tb_ctxmenu, (tab, data) => this.tabbar_ctx_menu_handle(tab, data, right_editor)); + this.eum.add(left_editor).add(right_editor); + this.eum.onstatuschange = (st) => this.updateStatus(st); + $(wrapper).css('visibility', 'visible'); + this.setup(); + this.eum.active.openFile(file); + } + /** + * Get the context menu items + */ + get tb_ctxmenu() { + return [ + { text: "__(Close)", id: "close" }, + { text: "__(Close All)", id: "close-all" }, + { text: "__(Move to other side)", id: "mv-side" }, + ]; + } + tabbar_ctx_menu_handle(tab, data, model) { + switch (data.id) { + case "close": + if (!tab) { + return; + } + model.closeTab(tab); + break; + case "close-all": + model.closeAll(); + break; + case "mv-side": + if (!tab) { + return; + } + let other_model = this.eum.editors[0]; + if (model == other_model) { + other_model = this.eum.editors[1]; + } + other_model.openFile(tab.data); + model.closeTab(tab); + if (this.split_mode == false) { + this.toggleSplitMode(); + } + break; + default: + break; + } + } + /** + * Set up the text editor + * + * @private + * @returns {void} + * @memberof Antedit + */ + setup() { + this.sidebar_container.selectedIndex = 0; + this.extension_list_view.itemtag = "afx-antedit-ext-list-item"; + this.fileview.onfileopen = (e) => { + if (!e.data || !e.data.path) { + return; + } + this.addRecent(e.data.path); + if (e.data.type === "dir") { + return; + } + 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) => { + let file = this + .fileview.selectedFile; + const items = [ + { text: "__(New file)", id: "new" }, + { text: "__(New folder)", id: "newdir" }, + { text: "__(Rename)", id: "rename" }, + { text: "__(Delete)", id: "delete" }, + { text: "__(Upload)", id: "upload" }, + ]; + if (file && file.type === "file") { + items.push({ text: "__(Select for compare)", id: "diff-org" }); + items.push({ text: "__(Compare with selected)", id: "diff-mod" }); + } + m.items = items; + 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) => { + if (!e.data.from || !e.data.to) { + return; + } + const src = e.data.from[0].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[0].parent.update(p2); + } + else { + e.data + .from[0].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; + } + const extension = { + name: "Editor", + text: __("Editor") + }; + const action = { + name: "langmode", + text: __("Change language mode"), + shortcut: 'CTRL-K' + }; + this.eum.addAction(extension, action, async (e) => { + try { + const data = await this.openDialog("SelectionDialog", { + "title": __("Select language"), + data: this.eum.active.getModes() + }); + this.eum.active.setMode(data); + } + catch (e) { + console.log(e); + } + }); + $(this.find("txt_ext_search")).keyup((e) => this.extension_search(e)); + this.loadExtensionMetaData(); + this.toggleSideBar(); + this.toggleSplitMode(); + this.applyAllSetting(); + } + /** + * Search an extension from the extension list + * + * @private + * @meberof Antedit + */ + extension_search(e) { + let k; + const search_box = this.find("txt_ext_search"); + switch (e.which) { + case 37: + return e.preventDefault(); + case 38: + this.extension_list_view.selectPrev(); + return e.preventDefault(); + case 39: + return e.preventDefault(); + case 40: + this.extension_list_view.selectNext(); + return e.preventDefault(); + case 13: + return e.preventDefault(); + default: + var text = search_box.value; + var result = []; + if (text.length === 2) { + this.extension_list_view.data = this.extension_meta_data; + return; + } + if (text.length < 3) { + return; + } + var term = new RegExp(text, "i"); + for (k in this.extension_meta_data) { + if (this.extension_meta_data[k].text.match(term)) { + result.push(this.extension_meta_data[k]); + } + } + this.extension_list_view.data = result; + } + } + /** + * Refresh editor extensions list on the side bar + * + * @private + * @memberof Antedit + */ + refreshExtensionRepositories() { + const promises = []; + const meta_file = `${this.meta().path}/extensions/extensions.json`; + for (let url of [meta_file].concat(this.setting.extension_repos)) { + promises.push(url.asFileHandle().read('json')); + } + Promise.all(promises) + .then((results) => { + const meta = {}; + for (let el of results.shift()) { + meta[el.name] = el; + } + this.extension_meta_data = []; + for (let result of results) { + for (let ext of result) { + if (meta[ext.name]) { + ext.installed = meta[ext.name].version; + } + ext.install_action = (url, callback) => { + (new Antedit.extensions["EditorExtensionMaker"](this)) + .installZip(url) + .then(() => { + this.loadExtensionMetaData(); + if (callback) { + callback(ext.version); + } + this.notify(__("Extension '{0}' installed", ext.text)); + }) + .catch((error) => { + this.error(__("Unable to install '{0}': {1}", ext.text, error.toString()), error); + }); + }; + ext.uninstall_action = (name, callback) => { + (new Antedit.extensions["EditorExtensionMaker"](this)) + .uninstall(name) + .then(() => { + this.loadExtensionMetaData(); + if (callback) { + callback(); + } + this.notify(__("Extension '{0}' uninstalled", name)); + }) + .catch((error) => { + this.error(__("Unable to uninstall '{0}': {1}", name, error.toString()), error); + }); + }; + this.extension_meta_data.push(ext); + } + } + this.extension_list_view.data = this.extension_meta_data; + }) + .catch((error) => { + this.error(__("Unable to read extension from repositories: {0}", error.toString()), error); + }); + } + /** + * 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; + this.refreshExtensionRepositories(); + } + else { + $(this.sidebar).hide(); + } + this.trigger("resize"); + } + showOutput(toggle = false) { + if (toggle) + this.showBottomBar(true); + this.bottombar.selectedIndex = 0; + } + openDiff(files) { + const diff_file = new OS.API.VFS.DiffEditorFileHandle(files); + this.eum.active.openFile(diff_file); + } + /** + * 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); + } + /** + * Toogle split mode + * + * #memberof Antedit + **/ + 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(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(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(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(e)); + } + }); + break; + case "upload": + if (!dir) { + return; + } + dir.upload() + .then((d) => { + this.notify(__("File uploaded to: {0}", dir.path)); + return this.fileview.update(dir.path); + }) + .catch((e) => this.error(__("Unable to upload file: {e}", e.toString()), __e(e))); + break; + case "diff-org": + if (!file) + return; + this.diff_buffer[0] = file.path.asFileHandle(); + break; + case "diff-mod": + if (!file) + return; + if (!this.diff_buffer[0]) + return; + this.diff_buffer[1] = file.path.asFileHandle(); + this.openDiff(this.diff_buffer); + 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.unshift(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} + * @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; + } + /** + * Add an action to the editor + * + * @param {GenericObject} extension + * @param {GenericObject} action + * @param callback + * @memberof EditorModelManager + */ + addAction(extension, action, callback) { + const ed_action = { + id: `${extension.name}:${action.name}`, + label: `${extension.text.__()}: ${action.text.__()}`, + keybindings: [], + precondition: null, + keybindingContext: null, + contextMenuGroupId: extension.name, + //contextMenuOrder: 1.5, + run: () => callback(extension.name, action.name) + }; + if (action.shortcut) { + const keys = action.shortcut.split("-"); + let binding = 0; + for (const key of keys) { + switch (key) { + case "CTRL": + binding = binding | monaco.KeyMod.CtrlCmd; + break; + case "ALT": + binding = binding | monaco.KeyMod.Alt; + break; + case "SHIFT": + binding = binding | monaco.KeyMod.Shift; + break; + case "SUPPER": + binding = binding | monaco.KeyMod.WinCtrl; + break; + default: + const k = `Key${key}`; + if (monaco.KeyCode[k]) { + binding = binding | monaco.KeyCode[k]; + } + else { + binding = 0; + } + } + } + if (binding != 0) + ed_action.keybindings.push(binding); + } + 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 = $("
")
+                    .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) {
+                if (s.match(/warn/i)) {
+                    this.log("warn", s, false);
+                }
+                else if (s.match(/error/i)) {
+                    this.log("error", s, false);
+                }
+                else {
+                    this.log("info", s, false);
+                }
+            }
+            /**
+             * Empty the log container
+             *
+             * @memberof Logger
+             */
+            clear() {
+                $(this.target).empty();
+            }
+        }
+        Antedit.Logger = Logger;
+        Antedit.dependencies = [
+            "pkg://MonacoCore/path.js",
+            "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}
+         * @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>}
+         * @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 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)));
+        }
+        /**
+         *
+         *
+         * @param {string} name extension name
+         * @returns {Promise}
+         * @memberof EditorExtensionMaker
+         */
+        uninstall(name) {
+            return new Promise(async (resolve, reject) => {
+                try {
+                    const ext_path = `${this.app.meta().path}/extensions`;
+                    const fp = `${ext_path}/extensions.json`.asFileHandle();
+                    const meta = await fp.read("json");
+                    let ext_meta = undefined;
+                    let ext_index = undefined;
+                    for (let idx in meta) {
+                        if (meta[idx].name === name) {
+                            ext_meta = meta[idx];
+                            ext_index = idx;
+                            break;
+                        }
+                    }
+                    if (ext_meta === undefined) {
+                        return resolve();
+                    }
+                    // remove the directory
+                    await `${ext_path}/${name}`.asFileHandle().remove();
+                    // update the extension file
+                    meta.splice(ext_index, 1);
+                    fp.cache = meta;
+                    await fp.write('object');
+                    resolve();
+                }
+                catch (e) {
+                    reject(e);
+                }
+            });
+        }
+        /**
+         *
+         *
+         * @param {string} path
+         * @returns {Promise}
+         * @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));
+                    // uninstall if exists
+                    await this.uninstall(meta.name);
+                    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} meta
+         * @returns {Promise}
+         * @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} meta
+         * @returns {Promise}
+         * @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 = {}));
diff --git a/GitGrapth/LibGitGraph.ts b/GitGrapth/LibGitGraph.ts
new file mode 100644
index 0000000..36b807b
--- /dev/null
+++ b/GitGrapth/LibGitGraph.ts
@@ -0,0 +1,645 @@
+namespace OS {
+    export namespace API
+    {
+        export interface LibGitGraphOptions {
+            commits_per_page?: number,
+            x_offset?: number,
+            y_offset?: number,
+            target: HTMLElement,
+            popup_height?: number
+        }
+        interface CommitData
+        {
+            hashes: {
+                commit: string,
+                parents: string,
+                tree: string
+            },
+            author: {
+                date: string,
+                name: string,
+                email: string
+            },
+            committer:{
+                date: string,
+                name: string,
+                email: string
+            },
+            extra: string,
+            message: string,
+            branches?: string[],
+            // helper values for redering
+            domel?: HTMLParagraphElement,
+            cx?: number,
+            color?: string
+        };
+        interface LineData
+        {
+            x_offset: number,
+            y_offset: number,
+            next_commit: string,
+            current_commit: string,
+            beginning: boolean,
+            color: string,
+        }
+        type OpenDiffCallback = (file: VFS.BaseFileHandle[]) => void;
+        interface LineConverging
+        {
+            src: LineData[],
+            dest: LineData
+        }
+        export class LibGitGraph
+        {
+            private _base_dir: VFS.BaseFileHandle;
+            private options: LibGitGraphOptions;
+            private lines_data: LineData[];
+            private commits: GenericObject;
+            private oldest_commit_date: string;
+            private svg_element: SVGElement;
+            private commits_list_element: HTMLDivElement;
+            private current_y_offset: number;
+            private load_more_el: HTMLParagraphElement;
+            private commit_detail_el: HTMLDivElement;
+            private current_head: CommitData;
+            private _on_open_diff:OpenDiffCallback;
+            constructor(option: LibGitGraphOptions)
+            {
+                this._base_dir = undefined;
+                this.lines_data = [];
+                this.commits = {};
+                this.oldest_commit_date = undefined;
+                this.svg_element = undefined;
+                this.commits_list_element = undefined;
+                this.load_more_el = undefined;
+                this.commit_detail_el = undefined;
+                this.current_head = undefined;
+                this._on_open_diff = undefined;
+                this.options = {
+                    commits_per_page: 100,
+                    x_offset: 24,
+                    y_offset: 24,
+                    target: undefined,
+                    popup_height: 250
+                };
+                for(const k in option)
+                {
+                    this.options[k] = option[k];
+                }
+                this.current_y_offset = this.options.y_offset;
+                this.init_graph();
+            }
+            set base_dir(v: VFS.BaseFileHandle)
+            {
+                this._base_dir = v;
+                this.render_next();
+
+            }
+            private gen_color(x:number): string
+            {
+                let n = x + 11;
+                const rgb = [0, 0, 0];
+                for (let i = 0; i < 24; i++) {
+                    rgb[i%3] <<= 1;
+                    rgb[i%3] |= n & 0x01;
+                    n >>= 1;
+                }
+                return '#' + rgb.reduce((a, c) => (c > 0x0f ? c.toString(16) : '0' + c.toString(16)) + a, '');
+
+            }
+            private meta(): PackageMetaType
+            {
+                return OS.setting.system.packages['GitGraph'];
+            }
+            private call(request: GenericObject): Promise {
+                return new Promise(async (ok, reject) => {
+                     request.args.base_dir = this._base_dir.path;
+                    let cmd = {
+                        path: this.meta().path + "/api/api.lua",
+                        parameters: request
+                    }
+                    let data = await API.apigateway(cmd, false);
+                    if(!data.error)
+                    {
+                        ok(data.result);
+                    }
+                    else
+                    {
+                        reject(API.throwe(__("LibGitGrapth server call error: {0}", data.error)));
+                    }
+                });
+            }
+
+            private load(before?: string): Promise
+            {
+                let request = {
+                    action: 'log',
+                    args: {
+                        n_commits: this.options.commits_per_page.toString(),
+                        before: before?before:null
+                    }
+                }
+                return this.call(request);
+            }
+            private error(e: Error)
+            {
+                announcer.oserror(__("GitGraph error: {0}", e.toString()), e);
+            }
+            set on_open_diff(c: OpenDiffCallback)
+            {
+                this._on_open_diff = c;
+            }
+            private init_graph()
+            {
+                if(!this.options.target)
+                {
+                    return  this.error(API.throwe("Target element is undefined"));
+                }
+                $(this.options.target)
+                    .css("overflow-y", "auto")
+                    .css("overflow-x", "hidden")
+                    .css("display", "block")
+                    .css("position", "relative");
+                this.svg_element = this.make_svg_el("svg",{
+                    width:  this.options.x_offset,
+                    height: this.options.y_offset
+                });
+                $(this.svg_element)
+                    .css("display", "block")
+                    .css("position", "absolute")
+                    .css("left", "0")
+                    //s.css("z-index", 10)
+                    .css("top", "0");
+                $(this.options.target).empty();
+                this.options.target.appendChild(this.svg_element);
+                const div = $("
") + .css("position", "absolute") + .css("left", "0") + .css("top", "0") + .css("width", "100%") + .css("padding-top",`${this.options.y_offset / 2}px`); + this.commits_list_element = div[0] as HTMLDivElement; + this.options.target.appendChild(this.commits_list_element); + + const p = $("

") + .css("height", `${this.options.y_offset}px`) + .css("display", "block") + .css("padding", "0") + .css("margin", "0") + .css("line-height",`${this.options.y_offset}px`) + .css("vertical-align", "middle"); + p.addClass("git_grapth_load_more"); + p.on("click", (e) => this.render_next()); + p.text(__("More").__()); + this.load_more_el = p[0] as HTMLParagraphElement; + this.commits_list_element.appendChild(this.load_more_el); + + const popup = $("

") + .css("position", "absolute") + .css("top", "0") + .css("height", this.options.popup_height + "px") + .css("display", "none") + .css("user-select", "text") + .addClass("git_grapth_commit_detail"); + this.commit_detail_el = popup[0] as HTMLDivElement; + this.options.target.appendChild(this.commit_detail_el); + + } + private render_next() + { + this.load(this.oldest_commit_date) + .then((data: CommitData[]) => + { + if(this.oldest_commit_date) + { + // remove the first commit as it is already in + // the graph + data.shift(); + } + this.draw_graph(data); + }) + .catch(e=>this.error(e)) + } + + private make_svg_el(tag: string, attrs: GenericObject) { + const el= document.createElementNS('http://www.w3.org/2000/svg', tag); + for (var k in attrs) + el.setAttribute(k, attrs[k]); + return el; + } + private max_line_off_x():number + { + if(this.lines_data.length == 0) + return 0; + return Math.max.apply(Math, this.lines_data.map((o) => o.x_offset)) + } + private update_line_data(commit: CommitData, y_offset: number): LineConverging + { + const parent_commits = commit.hashes.parents.split(" "); + // get the list of child lines + const children = this.lines_data.filter((line) => line.next_commit == commit.hashes.commit); + let merge: LineConverging = { + src: [], + dest: undefined + } + if (children.length === 0 ) + { + // add new line + let line: LineData = { + next_commit: parent_commits[0], + x_offset: this.max_line_off_x() + this.options.x_offset, + current_commit: commit.hashes.commit, + beginning: true, + y_offset: y_offset , + color: this.gen_color(this.lines_data.length), + }; + this.lines_data.push(line); + merge.dest = line; + } + else + { + let min_offset_x = Math.min.apply(Math, children.map((o) => o.x_offset)); + let line: LineData = undefined; + for (let el of children){ + if(el.x_offset == min_offset_x) + { + line = el; + line.next_commit = parent_commits[0]; + line.current_commit = commit.hashes.commit; + line.y_offset = y_offset; + } + else + { + this.lines_data.splice(this.lines_data.indexOf(el), 1); + merge.src.push(el); + } + } + merge.dest = line; + } + if(parent_commits.length === 2) + { + let line: LineData = undefined; + line = this.lines_data.filter(l=>l.next_commit == parent_commits[1])[0]; + if(!line) + { + // add new line + line = { + next_commit: parent_commits[1], + x_offset: this.max_line_off_x() + this.options.x_offset, + current_commit: commit.hashes.commit, + beginning: true, + y_offset: y_offset + this.options.y_offset, + color: this.gen_color(this.lines_data.length), + }; + this.lines_data.push(line); + } + else + { + line.y_offset = y_offset + this.options.y_offset; + } + merge.src.push(line); + } + return merge; + } + private draw_line(_x1: number,_y1: number, _x2: number, _y2: number, color: string, stroke?:number): SVGElement + { + let line_opt: GenericObject ={ + stroke: color, + fill: 'none', + "stroke-width": 1.5 + }; + if(stroke) + { + line_opt['stroke-width'] = stroke; + } + if(_x1 == _x2) + { + line_opt.d = `M ${_x1},${_y1} L ${_x2},${_y2}`; + } + else + { + let x1 = _x1; + let y1 = _y1; + let x2 = _x2; + let y2 = _y2; + let dx = Math.abs(x2-x1); + let dy = Math.abs(y2 -y1); + if(_y1 < _y2) + { + x1 = _x2; + y1 = _y2; + x2 = _x1; + y2 = _y1; + } + line_opt.d = `M ${x1},${y1} C ${x1},${y1 - dy} ${x2},${y2 + dy} ${x2},${y2}`; + } + const line = this.make_svg_el("path", line_opt); + return line; + } + private gen_commit_data_header(name: string| FormattedString, value: string): HTMLParagraphElement + { + const p = $("

") + .css("display","block"); + p[0].innerHTML = `${name.__()}: ${value}`; + return p[0] as HTMLParagraphElement; + } + private open_popup(commit: CommitData) + { + const el = commit.domel; + if(!el) return; + $(this.commit_detail_el).empty(); + const position = $(el).position(); + const bbox = (this.svg_element as SVGAElement).getBBox(); + const off_left = bbox.x + bbox.width + this.options.x_offset / 2; + const svg = this.make_svg_el("svg",{ + width: off_left-commit.cx + 5, + height: this.options.y_offset + }); + $(svg) + .css("display", "block") + .css("position", "absolute") + .css("left", "0") + .css("top", "-2px"); + svg.appendChild(this.draw_line( + 0, this.options.y_offset/2, + off_left-commit.cx, this.options.y_offset/2, + commit.color, + )); + /* + svg.appendChild( + this.make_svg_el("circle",{ + cx: off_left-commit.cx - 1, + cy: this.options.y_offset/2, + r:4, + fill: commit.color, + "stroke-width": 0.0 + }) + );*/ + $(this.commit_detail_el) + .css("border", "2px solid " + commit.color) + .css("color", commit.color) + .append( + $("

") + .css("position","absolute") + .css("height", this.options.y_offset) + .css("left", commit.cx-off_left) + .css("padding-left", off_left-commit.cx) + .append(svg) + .append($("").text('[X]') + .css("cursor", "pointer")) + .addClass("git_grapth_commit_detail_ctrl") + .on("click",(e) =>{ + $(this.commit_detail_el) + .css("display", "none") + .empty(); + })); + const left = $("
") + .css("display", "block") + .css("overflow-y", "auto") + .css("overflow-x", "hidden") + .css("flex", "1") + .css("border-right", "1px solid " + commit.color) + .addClass("git_grapth_commit_detail_left"); + const right = $("
") + .css("display", "block") + .css("overflow-y", "auto") + .css("overflow-x", "hidden") + .css("flex", "1") + .addClass("git_grapth_commit_detail_right"); + // display + left.append(this.gen_commit_data_header(__("Commit"), commit.hashes.commit)); + left.append(this.gen_commit_data_header(__("Parents"), commit.hashes.parents)); + left.append(this.gen_commit_data_header(__("Author"), `${commit.committer.name} <${commit.committer.email}>`)); + left.append(this.gen_commit_data_header(__("Date"), (new Date(commit.committer.date).toDateString()) )); + left.append( + $("
")
+                    .css("white-space", "pre-wrap")
+                    .text(commit.message));
+                this.commit_detail_el.appendChild(left[0]);
+                this.commit_detail_el.appendChild(right[0]);
+                this.list_file(commit.hashes.commit)
+                    .then((files) =>
+                    {
+                        const ul = $('