Improvement + bug fix

- Some minor bug fix
- Major change: allow split view in CodePad, make CodePad API portable so that it is easy to use another editor
other than ACE in the futures (such as monaco editor)
This commit is contained in:
lxsang
2020-12-20 16:45:51 +01:00
parent 6ee3861be6
commit 05cea66870
18 changed files with 749 additions and 625 deletions

View File

@ -0,0 +1,139 @@
var ace: any;
namespace OS {
export namespace application {
export class CodePadACEModel extends CodePadBaseEditorModel {
private modes: GenericObject<any>;
constructor(app: CodePad, tabbar: GUI.tag.TabBarTag, editorarea: HTMLElement) {
ace.config.set("basePath", "scripts/ace");
ace.require("ace/ext/language_tools");
super(app,tabbar,editorarea);
this.modes = ace.require("ace/ext/modelist");
}
getModes(): GenericObject<any>[] {
const list = [];
let v: GenericObject<any>;
for (v of Array.from(this.modes.modes)) {
list.push({ text: v.caption, mode: v.mode });
}
return list;
}
setTheme(theme: string): void {
this.editor.setTheme(theme);
}
protected setUndoManager(um: GenericObject<any>): void {
this.editor.getSession().setUndoManager(um);
}
protected setCursor(c: GenericObject<any>): void {
this.editor.renderer.scrollCursorIntoView(
{
row: c.row,
column: c.column,
},
0.5
);
this.editor.selection.moveTo(
c.row,
c.column
);
}
setMode(m: GenericObject<any>): void {
this.currfile.langmode = m;
this.editor.getSession().setMode(m.mode);
}
protected getCursor(): GenericObject<any> {
return this.editor.getCursorPosition();
}
protected newUndoManager(): GenericObject<any> {
return new ace.UndoManager();
}
/**
* Reference to the editor instance
*
* @protected
* @type {GenericObject<any>}
* @memberof CodePad
*/
protected editor: GenericObject<any>;
protected editorSetup(el: HTMLElement): void {
this.editor = ace.edit(el);
this.editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
highlightSelectedWord: true,
behavioursEnabled: true,
wrap: true,
fontSize: "10pt",
showInvisibles: true,
});
this.editor.setTheme("ace/theme/monokai");
this.editor.completers.push({
getCompletions(
editor: any,
session: any,
pos: any,
prefix: any,
callback: any
) { },
});
this.editor.getSession().setUseWrapMode(true);
}
on(evt_str: string, callback: () => void): void {
switch (evt_str) {
case "input":
case "focus":
this.editor.on(evt_str, callback);
break;
case "changeCursor":
this.editor
.getSession()
.selection.on(evt_str, callback);
break;
default:
break;
}
}
resize(): void {
this.editor.resize();
}
focus(): void {
this.editor.focus();
}
protected getModeForPath(path: string): GenericObject<any> {
const m = this.modes.getModeForPath(path);
return {
text: m.caption,
mode: m.mode
}
}
getEditorStatus(): GenericObject<any> {
const c = this.editor.session.selection.getCursor();
const l = this.editor.session.getLength();
return {
row: c.row,
column: c.column,
line: l,
langmode: this.currfile.langmode,
file: this.currfile.path
}
}
getValue(): string {
return this.editor.getValue();
}
setValue(value: string): void {
this.editor.setValue(value, -1);
}
}
}
}

View File

@ -153,7 +153,7 @@ namespace OS {
await this.mkfileAll(files, path, name);
this.app.currdir = rpath.asFileHandle();
this.app.toggleSideBar();
return this.app.openFile(
return this.app.eum.active.openFile(
`${rpath}/README.md`.asFileHandle() as application.CodePadFileHandle
);
} catch (e) {
@ -173,10 +173,10 @@ namespace OS {
*
* @private
* @param {string[]} list
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof AntOSDK
*/
private verify(list: string[]): Promise<any> {
private verify(list: string[]): Promise<void> {
return new Promise((resolve, reject) => {
if (list.length === 0) {
return resolve();
@ -242,10 +242,10 @@ namespace OS {
* @private
* @param {GenericObject<any>} meta
* @param {boolean} debug
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof AntOSDK
*/
private build(meta: GenericObject<any>, debug: boolean): Promise<any> {
private build(meta: GenericObject<any>, debug: boolean): Promise<void> {
const dirs = [
`${meta.root}/build`,
`${meta.root}/build/debug`,
@ -268,7 +268,7 @@ namespace OS {
})(),
src
);
await new Promise(async function (r, e) {
await new Promise<void>(async function (r, e) {
let code = jsrc;
if (!debug) {
const options = {
@ -306,7 +306,7 @@ namespace OS {
return e(__e(ex));
}
});
await new Promise(async (r, e) => {
await new Promise<void>(async (r, e) => {
const txt = await this.cat(
(() => {
const result1 = [];

View File

@ -0,0 +1,339 @@
namespace OS {
export namespace application {
export abstract class CodePadBaseEditorModel {
/**
* Reference to the current editing file handle
*
* @protected
* @type {CodePadFileHandle}
* @memberof CodePad
*/
protected currfile: CodePadFileHandle;
private app: CodePad;
/**
* Reference to the editor tab bar UI
*
* @private
* @type {GUI.tag.TabBarTag}
* @memberof CodePad
*/
private tabbar: GUI.tag.TabBarTag;
private container: HTMLElement;
onstatuschange: (stat: GenericObject<any>) => void;
/**
* Editor mutex
*
* @private
* @type {boolean}
* @memberof CodePad
*/
private editormux: boolean;
constructor(app: CodePad, tabbar: GUI.tag.TabBarTag, editorarea: HTMLElement) {
this.container = editorarea;
this.currfile = "Untitled".asFileHandle() as CodePadFileHandle;
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 {CodePadFileHandle} file then file handle to search
* @returns {number}
* @memberof CodePad
*/
private findTabByFile(file: CodePadFileHandle): 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 {CodePadFileHandle} file
* @memberof CodePad
*/
private newTab(file: CodePadFileHandle): void {
file.text = file.basename ? file.basename : file.path;
if (!file.cache) {
file.cache = "";
}
file.um = this.newUndoManager();
this.currfile.selected = false;
file.selected = true;
//console.log cnt
this.tabbar.push(file);
}
/**
* Close a tab when a file is closed
*
* @private
* @param {GUI.tag.ListViewItemTag} it reference to the tab to close
* @returns {boolean}
* @memberof CodePad
*/
private closeTab(it: GUI.tag.ListViewItemTag): boolean {
this.tabbar.delete(it);
const cnt = this.tabbar.items.length;
if (cnt === 0) {
this.openFile(
"Untitled".asFileHandle() as CodePadFileHandle
);
return false;
}
this.tabbar.selected = cnt - 1;
return false;
}
/**
* Select a tab by its index
*
* @private
* @param {number} i tab index
* @returns {void}
* @memberof CodePad
*/
private selecteTab(i: number): void {
//return if i is @tabbar.get "selidx"
const file = this.tabbar.items[i] as CodePadFileHandle;
if (!file) {
return;
}
//return if file is @currfile
if (this.currfile !== file) {
this.currfile.cache = this.getValue();
this.currfile.cursor = this.getCursor();
this.currfile.selected = false;
this.currfile = file;
}
if (!file.langmode) {
if (file.path.toString() !== "Untitled") {
file.langmode = this.getModeForPath(file.path);
} else {
file.langmode = {
text: "Text",
mode: "ace/mode/text",
};
}
}
this.editormux = true;
this.setUndoManager(this.newUndoManager());
this.setValue(file.cache);
this.setMode(file.langmode);
if (file.cursor) {
this.setCursor(file.cursor);
}
this.setUndoManager(file.um);
if (this.onstatuschange)
this.onstatuschange(this.getEditorStatus());
this.focus();
}
selectFile(file: CodePadFileHandle | string): void {
const i = this.findTabByFile(
file.asFileHandle() as CodePadFileHandle
);
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 {CodePadFileHandle} file file to open
* @returns {void}
* @memberof CodePad
*/
openFile(file: CodePadFileHandle): 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 {CodePadFileHandle} file
* @memberof CodePad
*/
private write(file: CodePadFileHandle): void {
this.currfile.cache = this.getValue();
file.write("text/plain")
.then((d) => {
file.dirty = false;
file.text = file.basename;
this.tabbar.update(undefined);
})
.catch((e) =>
this.app.error(__("Unable to save file: {0}", file.path), e)
);
}
save(): void {
this.currfile.cache = this.getValue();
if (this.currfile.basename) {
return this.write(this.currfile);
}
return this.saveAs();
}
/**
* Save the current file as another file
*
* @public
* @memberof CodePad
*/
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(this.currfile);
});
}
dirties(): CodePadFileHandle[] {
const result = [];
for (let v of Array.from(this.tabbar.items)) {
if (v.dirty) {
result.push(v);
}
}
return result;
}
set contextmenuHandle(cb:(e: any,m: any)=>void)
{
this.container.contextmenuHandle = cb;
}
closeAll(): void
{
this.tabbar.items = [];
this.setValue("");
this.setUndoManager(this.newUndoManager());
}
isDirty(): boolean
{
return this.dirties().length > 0;
}
protected abstract editorSetup(el: HTMLElement): void;
abstract on(evt_str: string, callback: () => void): void;
abstract resize(): void;
abstract focus(): void;
protected abstract getModeForPath(path: string): GenericObject<any>;
abstract getEditorStatus(): GenericObject<any>;
abstract getValue(): string;
abstract setValue(value: string): void;
protected abstract getCursor(): GenericObject<any>;
protected abstract newUndoManager(): GenericObject<any>;
protected abstract setUndoManager(um: GenericObject<any>): void;
abstract setMode(m: GenericObject<any>): void;
protected abstract setCursor(c: GenericObject<any>): void;
abstract setTheme(theme: string): void;
abstract getModes(): GenericObject<any>[];
}
}
}

View File

@ -137,8 +137,8 @@ namespace OS {
* @returns {Promise<any>}
* @memberof BaseExtension
*/
protected copy(files: string[], to: string): Promise<any> {
return new Promise((resolve, reject) => {
protected copy(files: string[], to: string): Promise<void> {
return new Promise((resolve, reject) =>{
if (files.length === 0) {
return resolve();
}
@ -287,7 +287,7 @@ namespace OS {
* @returns {Promise<any>}
* @memberof BaseExtension
*/
protected mkar(src: string, dest: string): Promise<any> {
protected mkar(src: string, dest: string): Promise<void> {
this.logger().info(__("Preparing for release"));
return new Promise((resolve, reject) => {
return new Promise(async (r, e) => {
@ -352,7 +352,7 @@ namespace OS {
* @returns {Promise<any>}
* @memberof BaseExtension
*/
protected mkdirAll(list: string[]): Promise<any> {
protected mkdirAll(list: string[]): Promise<void> {
return new Promise((resolve, reject) => {
if (list.length === 0) {
return resolve();
@ -388,7 +388,7 @@ namespace OS {
list: Array<string[]>,
path: string,
name: string
): Promise<any> {
): Promise<void> {
return new Promise((resolve, reject) => {
if (list.length === 0) {
return resolve();

View File

@ -135,7 +135,7 @@ namespace OS {
await this.mkfileAll(files, path, name);
this.app.currdir = rpath.asFileHandle();
this.app.toggleSideBar();
return this.app.openFile(
return this.app.eum.active.openFile(
`${rpath}/${name}.coffee`.asFileHandle() as application.CodePadFileHandle
);
} catch (e) {
@ -155,10 +155,10 @@ namespace OS {
*
* @private
* @param {string[]} list
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof ExtensionMaker
*/
private verify(list: string[]): Promise<any> {
private verify(list: string[]): Promise<void> {
return new Promise((resolve, reject) => {
if (list.length === 0) {
return resolve();
@ -220,10 +220,10 @@ namespace OS {
*
* @private
* @param {GenericObject<any>} meta
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof ExtensionMaker
*/
private build(meta: GenericObject<any>): Promise<any> {
private build(meta: GenericObject<any>): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const src = await this.compile(meta);
@ -239,7 +239,7 @@ namespace OS {
})(),
src
);
await new Promise((r, e) =>
await new Promise<void>((r, e) =>
`${meta.root}/build/debug/${meta.meta.name}.js`
.asFileHandle()
.setCache(jsrc)
@ -280,10 +280,10 @@ namespace OS {
*
* @private
* @param {GenericObject<any>} meta
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof ExtensionMaker
*/
private run(meta: GenericObject<any>): Promise<any> {
private run(meta: GenericObject<any>): Promise<void> {
return new Promise(async (resolve, reject) => {
const path = `${meta.root}/build/debug/${meta.meta.name}.js`;
if (API.shared[path]) {
@ -331,10 +331,10 @@ namespace OS {
* @private
* @param {string[]} files
* @param {*} zip
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof ExtensionMaker
*/
private installExtension(files: string[], zip: any): Promise<any> {
private installExtension(files: string[], zip: any): Promise<void> {
return new Promise((resolve, reject) => {
const idx = files.indexOf("extension.json");
if (idx < 0) {
@ -370,8 +370,8 @@ namespace OS {
private installFiles(
files: string[],
zip: any,
meta: GenericObject<any>
): Promise<any> {
meta: GenericObject<void>
): Promise<void> {
if (files.length === 0) {
return this.installMeta(meta);
}
@ -405,10 +405,10 @@ namespace OS {
*
* @private
* @param {GenericObject<any>} meta
* @returns {Promise<any>}
* @returns {Promise<void>}
* @memberof ExtensionMaker
*/
private installMeta(meta: GenericObject<any>): Promise<any> {
private installMeta(meta: GenericObject<any>): Promise<void> {
return new Promise(async (resolve, reject) => {
const file = `${this.app.meta().path
}/extensions.json`.asFileHandle();
@ -443,7 +443,7 @@ namespace OS {
* @returns {Promise<any>}
* @memberof ExtensionMaker
*/
private installZip(path: string): Promise<any> {
private installZip(path: string): Promise<void> {
return new Promise((resolve, reject) => {
this.import(["os://scripts/jszip.min.js"])
.then(() => {

View File

@ -1,4 +1,4 @@
module_files = main.js BaseExtension.js
module_files = main.js BaseExtension.js BaseEditorModel.js ACEModel.js
libfiles =

View File

@ -8,8 +8,17 @@
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox>
<afx-tab-bar closable="true" data-height="26" data-id = "tabbar"></afx-tab-bar>
<div data-id="datarea"></div>
<afx-hbox>
<afx-vbox data-id="left-panel">
<afx-tab-bar closable="true" data-height="26" data-id = "left-tabbar"></afx-tab-bar>
<div data-id="left-editorarea"></div>
</afx-vbox>
<afx-resizer data-width="3"></afx-resizer>
<afx-vbox data-id="right-panel">
<afx-tab-bar closable="true" data-height="26" data-id = "right-tabbar"></afx-tab-bar>
<div data-id="right-editorarea"></div>
</afx-vbox>
</afx-hbox>
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
@ -21,9 +30,9 @@
</afx-vbox>
</afx-hbox>
<div data-height="20" data-id="statctn">
<afx-label text=" " ></afx-label>
<afx-label data-id="langstat" ></afx-label>
<afx-label data-id="editorstat" ></afx-label>
<afx-label text=" " data-id = "current-file-lbl" style="float:left;"></afx-label>
<afx-label data-id="langstat" style="float:right; padding-right: 10px;"></afx-label>
<afx-label data-id="editorstat" style="float:right;"></afx-label>
</div>
</afx-vbox>
</afx-app-window>

View File

@ -91,7 +91,6 @@ afx-app-window[data-id = "codepad"] .afx-window-wrapper div[data-id="statctn"]{
}
afx-app-window[data-id = "codepad"] .afx-window-wrapper div[data-id="statctn"] afx-label {
float: right;
padding-left: 10px;
}
@ -161,6 +160,10 @@ afx-app-window[data-id = "codepad"] div[data-id="output-tab"] pre.code-pad-log-w
color: orange;
}
afx-app-window[data-id = "codepad"] div[data-id="output-tab"] pre.code-pad-log-info {
color: white;
}
afx-app-window[data-id = "codepad"] afx-button[ data-id="logger-clear" ] button{
border: 0;
background: transparent;

View File

@ -1,4 +1,3 @@
var ace: any;
namespace OS {
export namespace application {
/**
@ -69,14 +68,16 @@ namespace OS {
* @extends {BaseApplication}
*/
export class CodePad extends BaseApplication {
/**
* Reference to the current editing file handle
* Reference to the editor manager instance
*
* @private
* @type {CodePadFileHandle}
* @type {EditorModelManager}
* @memberof CodePad
*/
private currfile: CodePadFileHandle;
eum: EditorModelManager;
/**
* Reference to the current working directory
@ -121,14 +122,7 @@ namespace OS {
* @memberof CodePad
*/
private bottombar: GUI.tag.TabContainerTag;
/**
* Reference to the editor tab bar UI
*
* @private
* @type {GUI.tag.TabBarTag}
* @memberof CodePad
*/
private tabbar: GUI.tag.TabBarTag;
/**
* Reference to the language status bar
@ -149,31 +143,13 @@ namespace OS {
private editorstat: GUI.tag.LabelTag;
/**
* Reference to the editor instance
* Reference to the file status bar
*
* @private
* @type {GenericObject<any>}
* @type {GUI.tag.LabelTag}
* @memberof CodePad
*/
private editor: GenericObject<any>;
/**
* Editor language modes
*
* @private
* @type {GenericObject<any>}
* @memberof CodePad
*/
private modes: GenericObject<any>;
/**
* Editor mutex
*
* @private
* @type {boolean}
* @memberof CodePad
*/
private editormux: boolean;
private filestat: GUI.tag.LabelTag;
/**
* Reference to the CommandPalette's spotlight
@ -184,6 +160,15 @@ namespace OS {
spotlight: CMDMenu;
/**
* Is the split mode enabled
*
* @private
* @type {boolean}
* @memberof CodePad
*/
private split_mode: boolean;
/**
* Reference to the editor logger
*
@ -243,16 +228,7 @@ namespace OS {
*/
constructor(args: AppArgumentsType[]) {
super("CodePad", args);
this.currfile = "Untitled".asFileHandle() as CodePadFileHandle;
this.currdir = undefined;
if (this.args && this.args.length > 0) {
if (this.args[0].type === "dir") {
this.currdir = this.args[0].path.asFileHandle() as CodePadFileHandle;
} else {
this.currfile = this.args[0].path.asFileHandle() as CodePadFileHandle;
this.currdir = this.currfile.parent();
}
}
}
/**
@ -263,13 +239,31 @@ namespace OS {
*/
main(): void {
this.extensions = {};
this.eum = new EditorModelManager();
this.fileview = this.find("fileview") as GUI.tag.FileViewTag;
this.sidebar = this.find("sidebar") as GUI.tag.VBoxTag;
this.bottombar = this.find("bottombar") as GUI.tag.TabContainerTag;
this.tabbar = this.find("tabbar") as GUI.tag.TabBarTag;
this.langstat = this.find("langstat") as GUI.tag.LabelTag;
this.editorstat = this.find("editorstat") as GUI.tag.LabelTag;
this.filestat = this.find("current-file-lbl") as GUI.tag.LabelTag;
this.logger = new Logger(this.find("output-tab"));
this.split_mode = true;
// add editor instance
this.eum
.add(new CodePadACEModel(
this,
this.find("left-tabbar") as GUI.tag.TabBarTag,
this.find("left-editorarea")) as CodePadBaseEditorModel)
.add(new CodePadACEModel(
this,
this.find("right-tabbar") as GUI.tag.TabBarTag,
this.find("right-editorarea")) as CodePadBaseEditorModel);
this.eum.onstatuschange = (st) =>
this.updateStatus(st)
this.fileview.fetch = (path) =>
new Promise(async function (resolve, reject) {
let dir: API.VFS.BaseFileHandle;
@ -289,7 +283,17 @@ namespace OS {
return reject(__e(e));
}
});
return this.setup();
let file = "Untitled".asFileHandle() as CodePadFileHandle;
if (this.args && this.args.length > 0) {
if (this.args[0].type === "dir") {
this.currdir = this.args[0].path.asFileHandle() as CodePadFileHandle;
} else {
file = this.args[0].path.asFileHandle() as CodePadFileHandle;
this.currdir = file.parent();
}
}
this.setup();
return this.eum.active.openFile(file);
}
/**
@ -300,73 +304,6 @@ namespace OS {
* @memberof CodePad
*/
private setup(): void {
ace.config.set("basePath", "scripts/ace");
ace.require("ace/ext/language_tools");
this.editor = ace.edit(this.find("datarea"));
this.editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
highlightSelectedWord: true,
behavioursEnabled: true,
wrap: true,
fontSize: "10pt",
showInvisibles: true,
});
//themes = ace.require "ace/ext/themelist"
this.editor.setTheme("ace/theme/monokai");
this.modes = ace.require("ace/ext/modelist");
this.editor.completers.push({
getCompletions(
editor: any,
session: any,
pos: any,
prefix: any,
callback: any
) { },
});
this.editor.getSession().setUseWrapMode(true);
this.editormux = false;
this.editor.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.editor
.getSession()
.selection.on("changeCursor", (e: any) => {
return this.updateStatus();
});
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.openDialog("YesNoDialog", {
title: __("Close tab"),
text: __("Close without saving ?"),
}).then((d) => {
if (d) {
return this.closeTab(it);
}
return this.editor.focus();
});
return false;
};
this.fileview.onfileopen = (e) => {
if (!e.data || !e.data.path) {
return;
@ -374,7 +311,7 @@ namespace OS {
if (e.data.type === "dir") {
return;
}
return this.openFile(
return this.eum.active.openFile(
e.data.path.asFileHandle() as CodePadFileHandle
);
};
@ -386,19 +323,14 @@ namespace OS {
if (e.data.type === "dir") {
return;
}
const i = this.findTabByFile(
e.data.path.asFileHandle() as CodePadFileHandle
);
if (i !== -1) {
return (this.tabbar.selected = i);
}
this.eum.active.selectFile(e.data.path);
};
this.on("resize", () => this.editor.resize());
this.on("focus", () => this.editor.focus());
this.on("resize", () => this.eum.resize());
this.on("focus", () => this.eum.active.focus());
this.spotlight = new CMDMenu(__("Command palette"));
this.bindKey("ALT-P", () => this.spotlight.run(this));
this.find("datarea").contextmenuHandle = (e, m) => {
this.eum.contextmenuHandle = (e, m) => {
m.items = [
{
text: __("Command palete"),
@ -477,166 +409,8 @@ namespace OS {
this.loadExtensionMetaData();
this.initCommandPalete();
this.toggleSideBar();
this.toggleSplitMode();
this.applyAllSetting();
return this.openFile(this.currfile);
}
/**
* Open a file in new tab. If the file is already opened,
* the just select the tab
*
*
* @param {CodePadFileHandle} file file to open
* @returns {void}
* @memberof CodePad
*/
openFile(file: CodePadFileHandle): 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.error(
__("Unable to open: {0}", file.path),
e
);
});
}
/**
* Find a tab on the tabbar corresponding to a file handle
*
* @private
* @param {CodePadFileHandle} file then file handle to search
* @returns {number}
* @memberof CodePad
*/
private findTabByFile(file: CodePadFileHandle): 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 {CodePadFileHandle} file
* @memberof CodePad
*/
private newTab(file: CodePadFileHandle): void {
file.text = file.basename ? file.basename : file.path;
if (!file.cache) {
file.cache = "";
}
file.um = new ace.UndoManager();
this.currfile.selected = false;
file.selected = true;
//console.log cnt
this.tabbar.push(file);
}
/**
* Close a tab when a file is closed
*
* @private
* @param {GUI.tag.ListViewItemTag} it reference to the tab to close
* @returns {boolean}
* @memberof CodePad
*/
private closeTab(it: GUI.tag.ListViewItemTag): boolean {
this.tabbar.delete(it);
const cnt = this.tabbar.items.length;
if (cnt === 0) {
this.openFile(
"Untitled".asFileHandle() as CodePadFileHandle
);
return false;
}
this.tabbar.selected = cnt - 1;
return false;
}
/**
* Select a tab by its index
*
* @private
* @param {number} i tab index
* @returns {void}
* @memberof CodePad
*/
private selecteTab(i: number): void {
//return if i is @tabbar.get "selidx"
const file = this.tabbar.items[i] as CodePadFileHandle;
if (!file) {
return;
}
(this
.scheme as GUI.tag.WindowTag).apptitle = file.text.toString();
//return if file is @currfile
if (this.currfile !== file) {
this.currfile.cache = this.editor.getValue();
this.currfile.cursor = this.editor.selection.getCursor();
this.currfile.selected = false;
this.currfile = file;
}
if (!file.langmode) {
if (file.path.toString() !== "Untitled") {
const m = this.modes.getModeForPath(file.path);
file.langmode = { caption: m.caption, mode: m.mode };
} else {
file.langmode = {
caption: "Text",
mode: "ace/mode/text",
};
}
}
this.editormux = true;
this.editor.getSession().setUndoManager(new ace.UndoManager());
this.editor.setValue(file.cache, -1);
this.editor.getSession().setMode(file.langmode.mode);
if (file.cursor) {
this.editor.renderer.scrollCursorIntoView(
{
row: file.cursor.row,
column: file.cursor.column,
},
0.5
);
this.editor.selection.moveTo(
file.cursor.row,
file.cursor.column
);
}
this.editor.getSession().setUndoManager(file.um);
this.updateStatus();
this.editor.focus();
}
/**
@ -645,16 +419,17 @@ namespace OS {
* @private
* @memberof CodePad
*/
private updateStatus(): void {
const c = this.editor.session.selection.getCursor();
const l = this.editor.session.getLength();
private updateStatus(stat: GenericObject<any> = undefined): void {
if (!stat)
stat = this.eum.active.getEditorStatus();
this.editorstat.text = __(
"Row {0}, col {1}, lines: {2}",
c.row + 1,
c.column + 1,
l
stat.row + 1,
stat.column + 1,
stat.line
);
this.langstat.text = this.currfile.langmode.caption;
this.langstat.text = stat.langmode.text;
this.filestat.text = stat.file
}
/**
@ -713,6 +488,33 @@ namespace OS {
this.showBottomBar(!this.setting.showBottomBar);
}
private toggleSplitMode():void {
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() as CodePadFileHandle);
right_editor.focus();
}
this.trigger("resize");
}
/**
* Add an action to the [[CommandPalette]]'s spotlight
*
@ -758,26 +560,22 @@ namespace OS {
r: CodePad
) {
const data = d.data.item.data;
r.editor.setTheme(data.theme);
return r.editor.focus();
r.eum.active.setTheme(data.theme);
return r.eum.active.focus();
});
this.spotlight.addAction(cmdtheme);
const cmdmode = new CMDMenu(__("Change language mode"));
for (v of Array.from(this.modes.modes)) {
cmdmode.addAction({ text: v.caption, mode: v.mode });
for (v of Array.from(this.eum.active.getModes())) {
cmdmode.addAction({ text: v.text, mode: v.mode });
}
cmdmode.onchildselect(function (
d: GUI.TagEventType<GUI.tag.ListItemEventData>,
r: CodePad
) {
const data = d.data.item.data;
r.editor.session.setMode(data.mode);
r.currfile.langmode = {
caption: data.text,
mode: data.mode,
};
r.eum.active.setMode(data);
r.updateStatus();
r.editor.focus();
r.eum.active.focus();
});
this.spotlight.addAction(cmdmode);
this.addAction(CMDMenu.fromMenu(this.fileMenu()));
@ -1074,46 +872,9 @@ namespace OS {
}
}
/**
* Save a file
*
* @private
* @param {CodePadFileHandle} file
* @memberof CodePad
*/
private save(file: CodePadFileHandle): void {
file.write("text/plain")
.then((d) => {
file.dirty = false;
file.text = file.basename;
this.tabbar.update(undefined);
(this
.scheme as GUI.tag.WindowTag).apptitle = `${this.currfile.basename}`;
})
.catch((e) =>
this.error(__("Unable to save file: {0}", file.path), e)
);
}
/**
* Save the current file as another file
*
* @private
* @memberof CodePad
*/
private saveAs(): void {
this.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.save(this.currfile);
});
}
/**
* Menu action definition
@ -1131,7 +892,7 @@ namespace OS {
}
switch (dataid) {
case "new":
return me.openFile("Untitled".asFileHandle() as CodePadFileHandle);
return me.eum.active.openFile("Untitled".asFileHandle() as CodePadFileHandle);
case "open":
return me
.openDialog("FileDialog", {
@ -1141,7 +902,7 @@ namespace OS {
),
})
.then((f: API.FileInfoType) =>
me.openFile(f.file.path.asFileHandle())
me.eum.active.openFile(f.file.path.asFileHandle())
);
case "opendir":
return me
@ -1154,14 +915,10 @@ namespace OS {
return me.toggleSideBar();
});
case "save":
me.currfile.cache = me.editor.getValue();
if (me.currfile.basename) {
return me.save(me.currfile);
}
return me.saveAs();
return me.eum.active.save();
case "saveas":
me.currfile.cache = me.editor.getValue();
return me.saveAs();
return me.eum.active.saveAs();
default:
return console.log(dataid);
}
@ -1176,15 +933,7 @@ namespace OS {
*/
cleanup(evt: BaseEvent): void {
let v: GenericObject<any>;
const dirties = (() => {
const result = [];
for (v of Array.from(this.tabbar.items)) {
if (v.dirty) {
result.push(v);
}
}
return result;
})();
const dirties = this.eum.dirties();
if (dirties.length === 0) {
return;
}
@ -1231,6 +980,10 @@ namespace OS {
{
text: "__(Toggle bottom bar)",
dataid: "bottombar"
},
{
text: "__(Toggle split view)",
dataid: "splitview"
}
],
onchildselect: (
@ -1243,6 +996,10 @@ namespace OS {
case "bottombar":
return this.toggleBottomBar();
case "splitview":
return this.toggleSplitMode();
break;
default:
break;
@ -1355,7 +1112,102 @@ namespace OS {
return m;
};
/**
* Helper class to manager several instances
* of editor models
*
* @class EditorModelManager
*/
class EditorModelManager {
/**
* Referent to the active editor model
*
* @private
* @type {CodePadBaseEditorModel}
* @memberof EditorModelManager
*/
private active_editor: CodePadBaseEditorModel;
/**
* Store a list of editor models
*
* @private
* @type {CodePadBaseEditorModel[]}
* @memberof EditorModelManager
*/
private models: CodePadBaseEditorModel[];
/**
* Creates an instance of EditorModelManager.
* @memberof EditorModelManager
*/
constructor() {
this.active_editor = undefined;
this.models = [];
}
get editors(): CodePadBaseEditorModel[]{
return this.models;
}
set contextmenuHandle(cb: (e: any, m: any) => void) {
for (let ed of this.models) {
ed.contextmenuHandle = cb;
}
}
/**
* Get the active editor model
*
* @readonly
* @type {CodePadBaseEditorModel}
* @memberof EditorModelManager
*/
get active(): CodePadBaseEditorModel {
return this.active_editor;
}
/**
* Add a model to the manager
*
* @param {CodePadBaseEditorModel} model
* @memberof EditorModelManager
*/
add(model: CodePadBaseEditorModel): EditorModelManager {
this.models.push(model);
if (!this.active_editor)
this.active_editor = model;
model.on("focus", () => {
this.active_editor = model;
});
return this;
}
set onstatuschange(cb: (stat: GenericObject<any>) => void) {
for (let ed of this.models) {
ed.onstatuschange = cb;
}
}
dirties(): CodePadFileHandle[] {
let list = [];
for (let ed of this.models) {
list = list.concat(ed.dirties());
}
return list;
}
/**
* Resize all editor
*
* @memberof EditorModelManager
*/
resize(): void {
for (let ed of this.models) {
ed.resize();
}
}
}
/**
* This class handles log output to the Editor output container
*

View File

@ -7,7 +7,7 @@
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version":"0.0.3-b",
"version":"0.1.1-b",
"category":"Developments",
"iconclass":"fa fa-pencil-square-o",
"mimes":[