var texteditor = Object.copy(inputpanel, { title: "text editor", wh: [700,500], _cursor:0, /* Text cursor: [char,line] */ get cursor() { return this._cursor; }, set cursor(x) { if (x > this.value.length) x = this.value.length; if (x < 0) x = 0; this._cursor = x; this.line = this.get_line(); }, submit() {}, line: 0, killring: [], undos: [], startbuffer: "", savestate() { this.undos.push(this.value.slice()); }, popstate() { if (this.undos.length === 0) return; this.value = this.undos.pop(); this.cursor = this.cursor; }, copy(start, end) { return this.value.slice(start,end); }, delete_line(p) { var ls = this.line_start(p); var le = this.line_end(p)+1; this.cut_span(ls,le); this.to_line_start(); }, line_blank(p) { var ls = this.line_start(p); var le = this.line_end(p); var line = this.value.slice(ls, le); if (line.search(/[^\s]/g) === -1) return true; else return false; }, get_line() { var line = 0; for (var i = 0; i < this.cursor; i++) if (this.value[i] === "\n") line++; return line; }, start() { this.cursor = 0; this.startbuffer = this.value.slice(); }, get dirty() { return this.startbuffer !== this.value; }, src: "NEW FILE", guibody() { return [ Mum.text({str:`EDITING ${this.src}`}), Mum.text({str:this.value, caret:this.cursor, offset:[0,-16]}), ]; }, insert_char(char) { this.value = this.value.slice(0,this.cursor) + char + this.value.slice(this.cursor); this.cursor++; }, line_starting_whitespace(p) { var white = 0; var l = this.line_start(p); while (this.value[l] === " ") { white++; l++; } return white; }, cut_span(start, end) { if (end < start) return; this.savestate(); var ret = this.value.slice(start,end); this.value = this.value.slice(0,start) + this.value.slice(end); if (start > this.cursor) return ret; this.cursor -= ret.length; return ret; }, next_word(pos) { var v = this.value.slice(pos+1).search(/[^\w]\w/g); if (v === -1) return pos; return pos + v + 2; }, prev_word(pos) { while (this.value.slice(pos,pos+2).search(/[^\w]\w/g) === -1 && pos > 0) pos--; return pos+1; }, end_of_word(pos) { var l = this.value.slice(pos).search(/\w[^\w]/g); return l+pos; }, get inset() { return this.cursor - this.value.prev('\n', this.cursor) - 1; }, line_start(p) { return this.value.prev('\n', p)+1; }, line_end(p) { return this.value.next('\n', p); }, next_line(p) { return this.value.next('\n',p)+1; }, prev_line(p) { return this.line_start(this.value.prev('\n', p)); }, to_line_start() { this.cursor = this.value.prev('\n', this.cursor)+1; }, to_line_end() { var p = this.value.next('\n', this.cursor); if (p === -1) this.to_file_end(); else this.cursor = p; }, line_width(pos) { var start = this.line_start(pos); var end = this.line_end(pos); if (end === -1) end = this.value.length; return end-start; }, to_file_end() { this.cursor = this.value.length; }, to_file_start() { this.cursor = 0; }, desired_inset: 0, }); texteditor.inputs = {}; texteditor.inputs.char = function(char) { this.insert_char(char); this.keycb(); }, texteditor.inputs.enter = function(){ var white = this.line_starting_whitespace(this.cursor); this.insert_char('\n'); for (var i = 0; i < white; i++) this.insert_char(" "); }; texteditor.inputs.enter.rep = true; texteditor.inputs.backspace = function(){ this.value = this.value.slice(0,this.cursor-1) + this.value.slice(this.cursor); this.cursor--; }; texteditor.inputs.backspace.rep = true; texteditor.inputs['C-s'] = function() { if (this.srctype === 'function') { eval(`${this.src} = ${this.value}`); } }; texteditor.inputs['C-s'].doc = "Save edited text."; texteditor.inputs['C-u'] = function() { this.popstate(); }; texteditor.inputs['C-u'].doc = "Undo."; texteditor.inputs['C-q'] = function() { var ws = this.prev_word(this.cursor); var we = this.end_of_word(this.cursor)+1; var find = this.copy(ws, we); var obj = editor.edit_level.varname2obj(find); if (obj) { editor.unselect(); editor.selectlist.push(obj); } }; texteditor.inputs['C-q'].doc = "Select object of selected word."; texteditor.inputs['C-o'] = function() { this.insert_char('\n'); this.cursor--; }; texteditor.inputs['C-o'].doc = "Insert newline."; texteditor.inputs['C-o'].rep = true; texteditor.inputs['M-o'] = function() { while (this.line_blank(this.next_line(this.cursor))) this.delete_line(this.next_line(this.cursor)); while (this.line_blank(this.prev_line(this.cursor))) this.delete_line(this.prev_line(this.cursor)); }; texteditor.inputs['M-o'].doc = "Delete surround blank lines."; texteditor.inputs['C-d'] = function () { this.value = this.value.slice(0,this.cursor) + this.value.slice(this.cursor+1); }; texteditor.inputs['C-d'].doc = "Delete character."; texteditor.inputs['M-d'] = function() { this.cut_span(this.cursor, this.end_of_word(this.cursor)+1); }; texteditor.inputs['M-d'].doc = "Delete word."; texteditor.inputs['C-a'] = function() { this.to_line_start(); this.desired_inset = this.inset; }; texteditor.inputs['C-a'].doc = "To start of line."; texteditor.inputs['C-y'] = function() { if (this.killring.length === 0) return; this.insert_char(this.killring.pop()); }; texteditor.inputs['C-y'].doc = "Insert from killring."; texteditor.inputs['C-e'] = function() { this.to_line_end(); this.desired_inset = this.inset; }; texteditor.inputs['C-e'].doc = "To line end."; texteditor.inputs['C-k'] = function() { if (this.cursor === this.value.length-1) return; var killamt = this.value.next('\n', this.cursor) - this.cursor; var killed = this.cut_span(this.cursor-1, this.cursor+killamt); this.killring.push(killed); }; texteditor.inputs['C-k'].doc = "Kill from cursor to end of line."; texteditor.inputs['M-k'] = function() { var prevn = this.value.prev('\n', this.cursor); var killamt = this.cursor - prevn; var killed = this.cut_span(prevn+1, prevn+killamt); this.killring.push(killed); this.to_line_start(); }; texteditor.inputs['M-k'].doc = "Kill entire line the cursor is on."; texteditor.inputs['C-b'] = function() { this.cursor--; this.desired_inset = this.inset; }; texteditor.inputs['C-b'].rep = true; texteditor.inputs['M-b'] = function() { this.cursor = this.prev_word(this.cursor-2); this.desired_inset = this.inset; }; texteditor.inputs['M-b'].rep = true; texteditor.inputs['C-f'] = function() { this.cursor++; this.desired_inset = this.inset; }; texteditor.inputs['C-f'].rep = true; texteditor.inputs['M-f'] = function() { this.cursor = this.next_word(this.cursor); this.desired_inset = this.inset; }; texteditor.inputs['M-f'].rep = true; texteditor.inputs['C-p'] = function() { if (this.cursor === 0) return; this.desired_inset = Math.max(this.desired_inset, this.inset); this.cursor = this.prev_line(this.cursor); var newlinew = this.line_width(this.cursor); this.cursor += Math.min(this.desired_inset, newlinew); }; texteditor.inputs['C-p'].rep = true; texteditor.inputs['M-p'] = function() { while (this.line_blank(this.cursor)) this.cursor = this.prev_line(this.cursor); while (!this.line_blank(this.cursor)) this.cursor = this.prev_line(this.cursor); }; texteditor.inputs['M-p'].doc = "Go up to next line with text on it."; texteditor.inputs['M-p'].rep = true; texteditor.inputs['C-n'] = function() { if (this.cursor === this.value.length-1) return; if (this.value.next('\n', this.cursor) === -1) { this.to_file_end(); return; } this.desired_inset = Math.max(this.desired_inset, this.inset); this.cursor = this.next_line(this.cursor); var newlinew = this.line_width(this.cursor); this.cursor += Math.min(this.desired_inset, newlinew); }; texteditor.inputs['C-n'].rep = true; texteditor.inputs['M-n'] = function() { while (this.line_blank(this.cursor)) this.cursor = this.next_line(this.cursor); while (!this.line_blank(this.cursor)) this.cursor = this.next_line(this.cursor); }; texteditor.inputs['M-n'].doc = "Go down to next line with text on it."; texteditor.inputs['M-n'].rep = true; texteditor.open_fn = function(fnstr) { var fn = eval(fnstr); if (!fn) { console.warn(`${fnstr} is not a function.`); return; } this.src = fnstr; this.srctype = "function"; editor.openpanel(this); this.value = fn; this.cursor = 0; }