namespace OS { export namespace API { export namespace VFS { export class DiffEditorFileHandle extends OS.API.VFS.BaseFileHandle { text: string; textModel:any; selected: boolean; constructor(files: OS.API.VFS.BaseFileHandle[]) { 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(): Promise { 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); } }); } protected _rd(_t: string): Promise { 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); } }); } protected _wr(t: string): Promise { return new Promise((resolve, reject) => { resolve({ result: true, error: false, }) }); } setPath(s: string) { // do nothing } } } } export namespace application { /** * Extends the [[RemoteFileHandle]] interface with some useful * properties used by [[BaseEditorModel]] */ export type EditorFileHandle = OS.API.VFS.BaseFileHandle & { /** * The text will be displayed on the tab bar when opened * * @type {string} */ text: string; /** * Editor text model attached to the file * modification history of the file * * @type {any} */ textModel: any; /** * Indicate whether the file is selected * * @type {boolean} */ selected: boolean; }; export abstract class BaseEditorModel { /** * Reference to the current editing file handle * * @protected * @type {EditorFileHandle} * @memberof BaseEditorModel */ protected currfile: EditorFileHandle; /** * Referent to the parent app * * @private * @type {BaseApplication} * @memberof BaseEditorModel */ private app: BaseApplication; /** * Reference to the editor tab bar UI * * @private * @type {GUI.tag.TabBarTag} * @memberof BaseEditorModel */ private tabbar: GUI.tag.TabBarTag; /** * Referent to the editor container * * @private * @type {HTMLElement} * @memberof BaseEditorModel */ private container: HTMLElement; /** * Editor status monitoring */ onstatuschange: (stat: GenericObject) => void; /** * 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: BaseApplication, tabbar: GUI.tag.TabBarTag, editorarea: HTMLElement) { this.container = editorarea; this.currfile = "Untitled".asFileHandle() as EditorFileHandle; this.tabbar = tabbar; this.editorSetup(editorarea); this.app = app; this.editormux = false; this.onstatuschange = undefined; this.on("focus", () => { if (this.onstatuschange) this.onstatuschange(this.getEditorStatus()); }); this.on("input", () => { if (this.editormux) { this.editormux = false; return false; } if (!this.currfile.dirty) { this.currfile.dirty = true; this.currfile.text += "*"; return this.tabbar.update(undefined); } }); this.on("changeCursor", () => { if (this.onstatuschange) this.onstatuschange(this.getEditorStatus()); }); this.tabbar.ontabselect = (e) => { return this.selecteTab($(e.data.item).index()); }; this.tabbar.ontabclose = (e) => { const it = e.data.item; if (!it) { return false; } if (!it.data.dirty) { return this.closeTab(it); } this.app.openDialog("YesNoDialog", { title: __("Close tab"), text: __("Close without saving ?"), }).then((d) => { if (d) { return this.closeTab(it); } return this.focus(); }); return false; }; } /** * Find a tab on the tabbar corresponding to a file handle * * @private * @param {EditorFileHandle} file then file handle to search * @returns {number} * @memberof BaseEditorModel */ private findTabByFile(file: EditorFileHandle): number { 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 */ private newTab(file: EditorFileHandle): void { 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 const el = this.tabbar.push(file); this.app.trigger("tab-opened", el); } /** * 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: GUI.tag.ListViewItemTag): boolean { this.tabbar.delete(it); const cnt = this.tabbar.items.length; if (cnt === 0) { this.openFile( "Untitled".asFileHandle() as EditorFileHandle ); 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 */ private selecteTab(i: number): void { //return if i is @tabbar.get "selidx" const file = this.tabbar.items[i] as EditorFileHandle; 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: EditorFileHandle | string): void { const i = this.findTabByFile( file.asFileHandle() as EditorFileHandle ); 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: EditorFileHandle): void { //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 */ private write(): void { 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(): void { this.currfile.cache = this.getValue(); if (this.currfile.basename) { return this.write(); } return this.saveAs(); } /** * Reload the current openned file * * @return {*} {Promise} * @memberof BaseEditorModel */ reload(): Promise { return new Promise(async (ok,r) =>{ try { if (this.currfile.path.toString() === "Untitled") { return ok(true); } if(this.currfile.dirty) { const ret = await this.app.openDialog("YesNoDialog", { title: __("File modified"), text: __("Continue without saving ?"), }); if(!ret) { return ok(true); } } const d = await this.currfile.read(); this.currfile.cache = d || ""; this.currfile.dirty = false; this.currfile.text = this.currfile.basename ? this.currfile.basename : this.currfile.path; this.editormux = true; this.setValue(this.currfile.cache); this.tabbar.update(undefined); } catch(e){ this.app.error( __("Unable to open: {0}", this.currfile.path), e ); r(e); }; }) } /** * Save the current file as another file * * @public * @memberof BaseEditorModel */ saveAs(): void { 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(): EditorFileHandle[] { 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: (e: any, m: any) => void) { this.container.contextmenuHandle = cb; } /** * Close all opened files * * @memberof BaseEditorModel */ closeAll(): void { this.tabbar.items = []; this.openFile("Untitled".asFileHandle() as EditorFileHandle); this.resetEditor(); } /** * Check whether the editor is dirty * * @return {*} {boolean} * @memberof BaseEditorModel */ isDirty(): boolean { return this.dirties().length > 0; } /** * Set model tabbar menu context event * * @memberof BaseEditorModel */ setTabbarCtxMenu(items: GenericObject[], handle:(tab:GUI.tag.ListViewItemTag, mdata: GenericObject) => void): void { 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); } } /** * Set up the editor instance * Should be implemented by subclass * * @protected * @abstract * @param {HTMLElement} el * @memberof BaseEditorModel */ protected abstract editorSetup(el: HTMLElement): void; /** * Listen to editor event * * Should be implemented by subclasses * * @abstract * @param {string} evt_str * @param {() => void} callback * @memberof BaseEditorModel */ abstract on(evt_str: string, callback: () => void): void; /** * Resize the editor * * Should be implemented by subclasses * * @abstract * @memberof BaseEditorModel */ abstract resize(): void; /** * Make the editor focused * * Should be implemented by subclasses * * @abstract * @memberof BaseEditorModel */ abstract focus(): void; /** * Get language mode from file extension * * Should be implemented by subclasses * * @protected * @abstract * @param {string} path * @return {*} {GenericObject} * @memberof BaseEditorModel */ protected abstract getModeForPath(path: string): GenericObject; /** * Query the editor status * * Should be implemented by subclasses * * @abstract * @return {*} {GenericObject} * @memberof BaseEditorModel */ abstract getEditorStatus(): GenericObject; /** * Get the editor value * * Should be implemented by subclasses * * @abstract * @return {*} {string} * @memberof BaseEditorModel */ abstract getValue(): string; /** * Set the editor value * * Should be implemented by subclasses * * @abstract * @param {string} value * @memberof BaseEditorModel */ abstract setValue(value: string): void; /** * Set the editor language mode * * Should be implemented by subclasses * * @abstract * @param {GenericObject} m * @memberof BaseEditorModel */ abstract setMode(m: GenericObject): void; /** * Get textModel from the current editor session * * @protected * @abstract * @return {*} {*} * @memberof BaseEditorModel */ protected abstract getTexModel(): any; /** * Set text model to the current editor session * * @protected * @abstract * @param {*} model * @memberof BaseEditorModel */ protected abstract setTextModel(model: any): void; /** * Create new text model from the VFS file * * @protected * @abstract * @param {EditorFileHandle} file * @return {*} {*} * @memberof BaseEditorModel */ protected abstract newTextModelFrom(file: EditorFileHandle): any; /** * Reset the editor * * @protected * @abstract * @memberof BaseEditorModel */ protected abstract resetEditor(): void; /** * Set the current editor theme * * Should be implemented by subclasses * * @abstract * @param {string} theme * @memberof BaseEditorModel */ abstract setTheme(theme: string): void; /** * Get all language modes supported by the editor * * @abstract * @return {*} {GenericObject[]} * @memberof BaseEditorModel */ abstract getModes(): GenericObject[]; /** * Get the real editor model */ abstract getEditor(): any; } } }