1 struct editline
2 {
3     enum { CHUNKSIZE = 256 };
4 
5     char *text;
6     int len, maxlen;
7 
editlineeditline8     editline() : text(NULL), len(0), maxlen(0) {}
editlineeditline9     editline(const char *init) : text(NULL), len(0), maxlen(0)
10     {
11         set(init);
12     }
13 
emptyeditline14     bool empty() { return len <= 0; }
15 
cleareditline16     void clear()
17     {
18         DELETEA(text);
19         len = maxlen = 0;
20     }
21 
22     bool grow(int total, const char *fmt = "", ...)
23     {
24         if(total + 1 <= maxlen) return false;
25         maxlen = (total + CHUNKSIZE) - total%CHUNKSIZE;
26         char *newtext = new char[maxlen];
27         if(fmt)
28         {
29             va_list args;
30             va_start(args, fmt);
31             vformatstring(newtext, fmt, args, maxlen);
32             va_end(args);
33         }
34         else newtext[0] = '\0';
35         DELETEA(text);
36         text = newtext;
37         return true;
38     }
39 
40     void set(const char *str, int slen = -1)
41     {
42         if(slen < 0)
43         {
44             slen = strlen(str);
45             if(!grow(slen, "%s", str)) memcpy(text, str, slen + 1);
46         }
47         else
48         {
49             grow(slen);
50             memcpy(text, str, slen);
51             text[slen] = '\0';
52         }
53         len = slen;
54     }
55 
prependeditline56     void prepend(const char *str)
57     {
58         int slen = strlen(str);
59         if(!grow(slen + len, "%s%s", str, text ? text : ""))
60         {
61             memmove(&text[slen], text, len + 1);
62             memcpy(text, str, slen + 1);
63         }
64         len += slen;
65     }
66 
appendeditline67     void append(const char *str)
68     {
69         int slen = strlen(str);
70         if(!grow(len + slen, "%s%s", text ? text : "", str)) memcpy(&text[len], str, slen + 1);
71         len += slen;
72     }
73 
74     bool read(stream *f, int chop = -1)
75     {
76         if(chop < 0) chop = INT_MAX; else chop++;
77         set("");
78         while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len))
79         {
80             len += strlen(&text[len]);
81             if(len > 0 && text[len-1] == '\n')
82             {
83                 text[--len] = '\0';
84                 return true;
85             }
86             if(len + 1 >= maxlen && len + 1 < chop) grow(len + CHUNKSIZE, "%s", text);
87         }
88         if(len + 1 >= chop)
89         {
90             char buf[CHUNKSIZE];
91             while(f->getline(buf, sizeof(buf)))
92             {
93                 int blen = strlen(buf);
94                 if(blen > 0 && buf[blen-1] == '\n') return true;
95             }
96         }
97         return len > 0;
98     }
99 
deleditline100     void del(int start, int count)
101     {
102         if(!text) return;
103         if(start < 0) { count += start; start = 0; }
104         if(count <= 0 || start >= len) return;
105         if(start + count > len) count = len - start - 1;
106         memmove(&text[start], &text[start+count], len + 1 - (start + count));
107         len -= count;
108     }
109 
chopeditline110     void chop(int newlen)
111     {
112         if(!text) return;
113         len = clamp(newlen, 0, len);
114         text[len] = '\0';
115     }
116 
117     void insert(char *str, int start, int count = 0)
118     {
119         if(count <= 0) count = strlen(str);
120         start = clamp(start, 0, len);
121         grow(len + count, "%s", text ? text : "");
122         memmove(&text[start + count], &text[start], len - start + 1);
123         memcpy(&text[start], str, count);
124         len += count;
125     }
126 
combinelineseditline127     void combinelines(vector<editline> &src)
128     {
129         if(src.empty()) set("");
130         else loopv(src)
131         {
132             if(i) append("\n");
133             if(!i) set(src[i].text, src[i].len);
134             else insert(src[i].text, len, src[i].len);
135         }
136     }
137 };
138 
139 struct editor
140 {
141     enum { SCROLLEND = INT_MAX };
142 
143     int mode; //editor mode - 1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes)
144     bool active, rendered, unfocus;
145     const char *name;
146     const char *filename;
147 
148     int cx, cy; // cursor position - ensured to be valid after a region() or currentline()
149     int mx, my; // selection mark, mx=-1 if following cursor - avoid direct access, instead use region()
150     int maxx, maxy; // maxy=-1 if unlimited lines, 1 if single line editor
151 
152     int scrolly; // vertical scroll offset
153 
154     bool linewrap;
155     int pixelwidth; // required for up/down/hit/draw/bounds
156     int pixelheight; // -1 for variable sized, i.e. from bounds()
157 
158     vector<editline> lines; // MUST always contain at least one line!
159 
editoreditor160     editor(const char *name, int mode, const char *initval) :
161         mode(mode), active(true), rendered(false), unfocus(false), name(newstring(name)), filename(NULL),
162         cx(0), cy(0), mx(-1), my(-1), maxx(-1), maxy(-1), scrolly(mode==EDITORREADONLY ? SCROLLEND : 0), linewrap(false), pixelwidth(-1), pixelheight(-1)
163     {
164         //printf("editor %08x '%s'\n", this, name);
165         clear(initval ? initval : "");
166     }
167 
~editoreditor168     ~editor()
169     {
170         //printf("~editor %08x '%s'\n", this, name);
171         DELETEA(name);
172         DELETEA(filename);
173         clear(NULL);
174     }
175 
emptyeditor176     bool empty() { return lines.length() == 1 && lines[0].empty(); }
177 
178     void clear(const char *init = "")
179     {
180         cx = cy = 0;
181         mx = my = -1;
182         mark(false);
183         loopv(lines) lines[i].clear();
184         lines.shrink(0);
185         if(init)
186         {
187             lines.add().set(init);
188             cx = lines[0].len;
189         }
190     }
191 
updateheighteditor192     void updateheight()
193     {
194         int width;
195         text_bounds(lines[0].text, width, pixelheight, pixelwidth);
196     }
197 
setfileeditor198     void setfile(const char *fname)
199     {
200         DELETEA(filename);
201         if(fname) filename = newstring(fname);
202     }
203 
loadeditor204     void load()
205     {
206         if(!filename) return;
207         clear(NULL);
208         stream *file = openutf8file(filename, "r");
209         if(file)
210         {
211             while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy));
212             lines.pop().clear();
213             delete file;
214         }
215         if(lines.empty()) lines.add().set("");
216     }
217 
saveeditor218     void save()
219     {
220         if(!filename) return;
221         stream *file = openutf8file(filename, "w");
222         if(!file) return;
223         loopv(lines) file->putline(lines[i].text);
224         delete file;
225     }
226 
markeditor227     void mark(bool enable)
228     {
229         mx = enable ? cx : -1;
230         my = cy;
231     }
232 
selectalleditor233     void selectall()
234     {
235         mx = my = INT_MAX;
236         cx = cy = 0;
237     }
238 
239     // constrain results to within buffer - s=start, e=end, return true if a selection range
240     // also ensures that cy is always within lines[] and cx is valid
regioneditor241     bool region(int &sx, int &sy, int &ex, int &ey)
242     {
243         int n = lines.length();
244         if(!n)
245         {
246             lines.add().set("");
247             n = 1;
248         }
249         if(cy < 0) cy = 0;
250         else if(cy >= n) cy = n-1;
251         int len = lines[cy].len;
252         if(cx < 0) cx = 0;
253         else if(cx > len) cx = len;
254         if(mx >= 0)
255         {
256             if(my < 0) my = 0;
257             else if(my >= n) my = n-1;
258             len = lines[my].len;
259             if(mx > len) mx = len;
260         }
261         sx = (mx >= 0) ? mx : cx;
262         sy = (mx >= 0) ? my : cy;
263         ex = cx;
264         ey = cy;
265         if(sy > ey) { swap(sy, ey); swap(sx, ex); }
266         else if(sy == ey && sx > ex) swap(sx, ex);
267         return (sx != ex) || (sy != ey);
268     }
269 
regioneditor270     bool region() { int sx, sy, ex, ey; return region(sx, sy, ex, ey); }
271 
272     // also ensures that cy is always within lines[] and cx is valid
currentlineeditor273     editline &currentline()
274     {
275         int n = lines.length();
276         if(!n)
277         {
278             lines.add().set("");
279             n = 1;
280         }
281         if(cy < 0) cy = 0;
282         else if(cy >= n) cy = n-1;
283         if(cx < 0) cx = 0;
284         else if(cx > lines[cy].len) cx = lines[cy].len;
285         return lines[cy];
286     }
287 
copyselectiontoeditor288     void copyselectionto(editor *b)
289     {
290         if(b == this) return;
291 
292         b->clear(NULL);
293         int sx, sy, ex, ey;
294         region(sx, sy, ex, ey);
295         loopi(1+ey-sy)
296         {
297             if(b->maxy != -1 && b->lines.length() >= b->maxy) break;
298             int y = sy+i;
299             char *line = lines[y].text;
300             int len = lines[y].len;
301             if(y == sy && y == ey)
302             {
303                 line += sx;
304                 len = ex - sx;
305             }
306             else if(y == sy) line += sx;
307             else if(y == ey) len = ex;
308             b->lines.add().set(line, len);
309         }
310         if(b->lines.empty()) b->lines.add().set("");
311     }
312 
tostringeditor313     char *tostring()
314     {
315         int len = 0;
316         loopv(lines) len += lines[i].len + 1;
317         char *str = newstring(len);
318         int offset = 0;
319         loopv(lines)
320         {
321             editline &l = lines[i];
322             memcpy(&str[offset], l.text, l.len);
323             offset += l.len;
324             str[offset++] = '\n';
325         }
326         str[offset] = '\0';
327         return str;
328     }
329 
selectiontostringeditor330     char *selectiontostring()
331     {
332         vector<char> buf;
333         int sx, sy, ex, ey;
334         region(sx, sy, ex, ey);
335         loopi(1+ey-sy)
336         {
337             int y = sy+i;
338             char *line = lines[y].text;
339             int len = lines[y].len;
340             if(y == sy && y == ey)
341             {
342                 line += sx;
343                 len = ex - sx;
344             }
345             else if(y == sy) line += sx;
346             else if(y == ey) len = ex;
347             buf.put(line, len);
348             buf.add('\n');
349         }
350         buf.add('\0');
351         return newstring(buf.getbuf(), buf.length()-1);
352     }
353 
removelineseditor354     void removelines(int start, int count)
355     {
356         loopi(count) lines[start+i].clear();
357         lines.remove(start, count);
358     }
359 
deleditor360     bool del() // removes the current selection (if any)
361     {
362         int sx, sy, ex, ey;
363         if(!region(sx, sy, ex, ey))
364         {
365             mark(false);
366             return false;
367         }
368         if(sy == ey)
369         {
370             if(sx == 0 && ex == lines[ey].len) removelines(sy, 1);
371             else lines[sy].del(sx, ex - sx);
372         }
373         else
374         {
375             if(ey > sy+1) { removelines(sy+1, ey-(sy+1)); ey = sy+1; }
376             if(ex == lines[ey].len) removelines(ey, 1); else lines[ey].del(0, ex);
377             if(sx == 0) removelines(sy, 1); else lines[sy].del(sx, lines[sy].len - sx);
378         }
379         if(lines.empty()) lines.add().set("");
380         mark(false);
381         cx = sx;
382         cy = sy;
383         editline &current = currentline();
384         if(cx >= current.len && cy < lines.length() - 1)
385         {
386             current.append(lines[cy+1].text);
387             removelines(cy + 1, 1);
388         }
389         return true;
390     }
391 
inserteditor392     void insert(char ch)
393     {
394         del();
395         editline &current = currentline();
396         if(ch == '\n')
397         {
398             if(maxy == -1 || cy < maxy-1)
399             {
400                 editline newline(&current.text[cx]);
401                 current.chop(cx);
402                 cy = min(lines.length(), cy+1);
403                 lines.insert(cy, newline);
404             }
405             else current.chop(cx);
406             cx = 0;
407         }
408         else if(ch != '\r')
409         {
410             int len = current.len;
411             if(maxx < 0 || len <= maxx-1) current.insert(&ch, cx++, 1);
412         }
413     }
414 
inserteditor415     void insert(const char *s)
416     {
417         while(*s) insert(*s++);
418     }
419 
insertallfromeditor420     void insertallfrom(editor *b)
421     {
422         if(b == this) return;
423 
424         del();
425 
426         if(b->lines.length() == 1 || maxy == 1)
427         {
428             editline &current = currentline();
429             char *str = b->lines[0].text;
430             int slen = b->lines[0].len;
431             if(maxx >= 0 && b->lines[0].len + cx > maxx) slen = maxx-cx;
432             if(slen > 0)
433             {
434                 int len = current.len;
435                 if(maxx >= 0 && slen + cx + len > maxx) len = max(0, maxx-(cx+slen));
436                 current.insert(str, cx, slen);
437                 cx += slen;
438             }
439         }
440         else
441         {
442             loopv(b->lines)
443             {
444                 if(!i)
445                 {
446                     lines[cy++].append(b->lines[i].text);
447                 }
448                 else if(i >= b->lines.length())
449                 {
450                     cx = b->lines[i].len;
451                     lines[cy].prepend(b->lines[i].text);
452                 }
453                 else if(maxy < 0 || lines.length() < maxy) lines.insert(cy++, editline(b->lines[i].text));
454             }
455         }
456     }
457 
scrollupeditor458     void scrollup()
459     {
460         mark(false);
461         cy--;
462     }
463 
scrolldowneditor464     void scrolldown()
465     {
466         mark(false);
467         cy++;
468     }
keyeditor469     void key(int code)
470     {
471         switch(code)
472         {
473             case SDLK_UP:
474                 mark(false);
475                 if(linewrap)
476                 {
477                     int x, y;
478                     char *str = currentline().text;
479                     text_pos(str, cx+1, x, y, pixelwidth, TEXT_NO_INDENT);
480                     if(y > 0) { cx = text_visible(str, x, y-FONTH, pixelwidth, TEXT_NO_INDENT); break; }
481                 }
482                 cy--;
483                 break;
484             case SDLK_DOWN:
485                 mark(false);
486                 if(linewrap)
487                 {
488                     int x, y, width, height;
489                     char *str = currentline().text;
490                     text_pos(str, cx, x, y, pixelwidth, TEXT_NO_INDENT);
491                     text_bounds(str, width, height, 0, 0, pixelwidth, TEXT_NO_INDENT);
492                     y += FONTH;
493                     if(y < height) { cx = text_visible(str, x, y, pixelwidth, TEXT_NO_INDENT); break; }
494                 }
495                 cy++;
496                 break;
497             case SDLK_PAGEUP:
498                 mark(false);
499                 cy-=pixelheight/FONTH;
500                 break;
501             case SDLK_PAGEDOWN:
502                 mark(false);
503                 cy+=pixelheight/FONTH;
504                 break;
505             case SDLK_HOME:
506                 mark(false);
507                 cx = 0;
508                 break;
509             case SDLK_END:
510                 mark(false);
511                 cx = INT_MAX;
512                 break;
513             case SDLK_LEFT:
514                 mark(false);
515                 cx--;
516                 break;
517             case SDLK_RIGHT:
518                 mark(false);
519                 cx++;
520                 break;
521             case SDLK_DELETE:
522                 if(!del())
523                 {
524                     editline &current = currentline();
525                     if(cx < current.len) current.del(cx, 1);
526                     else if(cy < lines.length()-1)
527                     {   //combine with next line
528                         current.append(lines[cy+1].text);
529                         removelines(cy+1, 1);
530                     }
531                 }
532                 break;
533             case SDLK_BACKSPACE:
534                 if(!del())
535                 {
536                     editline &current = currentline();
537                     if(cx > 0) current.del(--cx, 1);
538                     else if(cy > 0)
539                     {   //combine with previous line
540                         cx = lines[cy-1].len;
541                         lines[cy-1].append(current.text);
542                         removelines(cy--, 1);
543                     }
544                 }
545                 break;
546             case SDLK_LSHIFT:
547             case SDLK_RSHIFT:
548                 break;
549             case SDLK_v:
550                 if(SDL_GetModState()&MOD_KEYS)
551                 {
552                     char *pastebuf = pastetext();
553                     if(pastebuf)
554                     {
555                         insert(pastebuf);
556                         delete[] pastebuf;
557                     }
558                 }
559                 break;
560             case SDLK_RETURN:
561                 insert('\n');
562                 break;
563         }
564     }
565 
inputeditor566     void input(const char *str, int len)
567     {
568         loopi(len) insert(str[i]);
569     }
570 
hiteditor571     void hit(int hitx, int hity, bool dragged)
572     {
573         int maxwidth = linewrap ? pixelwidth : -1, h = 0;
574         for(int i = scrolly; i < lines.length(); i++)
575         {
576             int width, height;
577             text_bounds(lines[i].text, width, height, 0, 0, maxwidth, TEXT_NO_INDENT);
578             if(h + height > pixelheight) break;
579 
580             if(hity >= h && hity <= h+height)
581             {
582                 int x = text_visible(lines[i].text, hitx, hity-h, maxwidth, TEXT_NO_INDENT);
583                 if(dragged) { mx = x; my = i; } else { cx = x; cy = i; };
584                 break;
585             }
586            h += height;
587         }
588     }
589 
limitscrollyeditor590     int limitscrolly()
591     {
592         int maxwidth = linewrap ? pixelwidth : -1, slines = lines.length();
593         for(int ph = pixelheight; slines > 0 && ph > 0;)
594         {
595             int width, height;
596             text_bounds(lines[slines-1].text, width, height, 0, 0, maxwidth, TEXT_NO_INDENT);
597             if(height > ph) break;
598             ph -= height;
599             slines--;
600         }
601         return slines;
602     }
603 
draweditor604     void draw(int x, int y, int color, int alpha, bool hit)
605     {
606         int h = 0, maxwidth = linewrap ? pixelwidth : -1,
607             starty = scrolly, sx = 0, sy = 0, ex = 0, ey = 0;
608         bool selection = region(sx, sy, ex, ey);
609         if(starty == SCROLLEND) // fix scrolly so that <cx, cy> is always on screen
610         {
611             cy = lines.length()-1;
612             starty = 0;
613         }
614         if(cy < starty) starty = cy;
615         else
616         {
617             int ch = 0;
618             for(int i = cy; i >= starty; i--)
619             {
620                 int width, height;
621                 text_bounds(lines[i].text, width, height, 0, 0, maxwidth, TEXT_NO_INDENT);
622                 ch += height;
623                 if(ch > pixelheight) { starty = i+1; break; }
624             }
625         }
626 
627         if(selection)
628         {
629             int psx, psy, pex, pey; // convert from cursor coords into pixel coords
630             text_pos(lines[sy].text, sx, psx, psy, maxwidth, TEXT_NO_INDENT);
631             text_pos(lines[ey].text, ex, pex, pey, maxwidth, TEXT_NO_INDENT);
632             int maxy = lines.length();
633             int h = 0;
634             for(int i = starty; i < maxy; i++)
635             {
636                 int width, height;
637                 text_bounds(lines[i].text, width, height, 0, 0, maxwidth, TEXT_NO_INDENT);
638                 if(h+height > pixelheight) { maxy = i; break; }
639                 if(i == sy) psy += h;
640                 if(i == ey) { pey += h; break; }
641                 h += height;
642             }
643             maxy--;
644 
645             if(ey >= starty && sy <= maxy)
646             {
647                 if(sy < starty) // crop top/bottom within window
648                 {
649                     sy = starty;
650                     psy = 0;
651                     psx = 0;
652                 }
653                 if(ey > maxy)
654                 {
655                     ey = maxy;
656                     pey = pixelheight-FONTH;
657                     pex = pixelwidth;
658                 }
659                 hudnotextureshader->set();
660                 gle::colorf(0.25f, 0.25f, 0.75f, alpha/255.f);
661                 gle::defvertex(2);
662                 gle::begin(GL_QUADS);
663                 if(psy == pey)
664                 {
665                     gle::attribf(x+psx, y+psy);
666                     gle::attribf(x+pex, y+psy);
667                     gle::attribf(x+pex, y+pey+FONTH);
668                     gle::attribf(x+psx, y+pey+FONTH);
669                 }
670                 else
671                 {   gle::attribf(x+psx, y+psy);
672                     gle::attribf(x+psx, y+psy+FONTH);
673                     gle::attribf(x+pixelwidth, y+psy+FONTH);
674                     gle::attribf(x+pixelwidth, y+psy);
675                     if(pey-psy > FONTH)
676                     {
677                         gle::attribf(x, y+psy+FONTH);
678                         gle::attribf(x+pixelwidth, y+psy+FONTH);
679                         gle::attribf(x+pixelwidth, y+pey);
680                         gle::attribf(x, y+pey);
681                     }
682                     gle::attribf(x, y+pey);
683                     gle::attribf(x, y+pey+FONTH);
684                     gle::attribf(x+pex, y+pey+FONTH);
685                     gle::attribf(x+pex, y+pey);
686                 }
687                 gle::end();
688                 hudshader->set(); //?
689             }
690         }
691 
692         for(int i = starty; i < lines.length(); i++)
693         {
694             int width, height;
695             text_bounds(lines[i].text, width, height, 0, 0, maxwidth, TEXT_NO_INDENT);
696             if(h+height > pixelheight) break;
697             draw_text(lines[i].text, x, y+h, color>>16, (color>>8)&0xFF, color&0xFF, alpha, TEXT_NO_INDENT, hit && (cy == i) ? cx : -1, maxwidth);
698             if(linewrap && height > FONTH) // line wrap indicator
699             {
700                 hudnotextureshader->set();
701                 gle::colorf(1, 1, 1, alpha/255.f);
702                 gle::defvertex(2);
703                 gle::begin(GL_TRIANGLE_STRIP);
704                 gle::attribf(x, y+h+FONTH);
705                 gle::attribf(x, y+h+height);
706                 gle::attribf(x-FONTW/4, y+h+FONTH);
707                 gle::attribf(x-FONTW/4, y+h+height);
708                 gle::end();
709                 hudshader->set();
710             }
711             h += height;
712         }
713     }
714 };
715 
716 // a 'stack' where the last is the current focused editor
717 static vector <editor*> editors;
718 static editor *textfocus = NULL;
719 
readyeditors()720 static void readyeditors()
721 {
722     loopv(editors) editors[i]->active = (editors[i]->mode>=EDITORFOREVER);
723 }
724 
flusheditors()725 static void flusheditors()
726 {
727     loopvrev(editors) if(!editors[i]->active)
728     {
729         editor *e = editors.remove(i);
730         if(e == textfocus) textfocus = NULL;
731         delete e;
732     }
733 }
734 
735 static editor *useeditor(const char *name, int mode, bool focus, const char *initval = NULL)
736 {
737     loopv(editors) if(!strcmp(editors[i]->name, name))
738     {
739         editor *e = editors[i];
740         if(focus) textfocus = e;
741         e->active = true;
742         return e;
743     }
744     if(mode < 0) return NULL;
745     editor *e = new editor(name, mode, initval);
746     editors.add(e);
747     if(focus) textfocus = e;
748     return e;
749 }
750 
751 #if 0
752 static editor *findeditor(const char *name)
753 {
754     loopv(editors) if(strcmp(editors[i]->name, name) == 0) return editors[i];
755     return NULL;
756 }
757 #endif // 0
758 
759 #define TEXTCOMMAND(f, s, d, body) ICOMMAND(0, f, s, d,\
760     if(!textfocus || identflags&IDF_WORLD) return;\
761     body\
762 )
763 
764 ICOMMAND(0, textlist, "", (), // @DEBUG return list of all the editors
765     vector<char> s;
766     loopv(editors)
767     {
768         if(i > 0) s.put(", ", 2);
769         s.put(editors[i]->name, strlen(editors[i]->name));
770     }
771     s.add('\0');
772     result(s.getbuf());
773 );
774 TEXTCOMMAND(textshow, "", (), // @DEBUG return the start of the buffer
775     editline line;
776     line.combinelines(textfocus->lines);
777     result(line.text);
778     line.clear();
779 );
780 ICOMMAND(0, textfocus, "sis", (char *name, int *mode, char *initval), // focus on a (or create a persistent) specific editor, else returns current name
781     if(identflags&IDF_WORLD) return;
782     if(*name) useeditor(name, *mode<=0 ? EDITORFOREVER : *mode, true, initval);
783     else if(editors.length() > 0) result(editors.last()->name);
784 );
785 TEXTCOMMAND(textprev, "", (), editors.insert(0, textfocus); editors.pop();); // return to the previous editor
786 TEXTCOMMAND(textmode, "i", (int *m), // (1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes)) topmost editor, return current setting if no args
787     if(*m) textfocus->mode = *m;
788     else intret(textfocus->mode);
789 );
790 TEXTCOMMAND(textsave, "s", (char *file),  // saves the topmost (filename is optional)
791     if(identflags&IDF_WORLD) return;
792     if(*file) textfocus->setfile(copypath(file));
793     textfocus->save();
794 );
795 TEXTCOMMAND(textload, "s", (char *file), // loads into the textfocusmost editor, returns filename if no args
796     if(identflags&IDF_WORLD) return;
797     if(*file)
798     {
799         textfocus->setfile(copypath(file));
800         textfocus->load();
801     }
802     else if(textfocus->filename) result(textfocus->filename);
803 );
804 ICOMMAND(0, textinit, "sss", (char *name, char *file, char *initval), // loads into named editor if no file assigned and editor has been rendered
805 {
806     if(identflags&IDF_WORLD) return;
807     editor *e = NULL;
808     loopv(editors) if(!strcmp(editors[i]->name, name)) { e = editors[i]; break; }
809     if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.length() == 1 && !strcmp(e->lines[0].text, initval))))
810     {
811         e->setfile(copypath(file));
812         e->load();
813     }
814 });
815 
816 #define PASTEBUFFER "#pastebuffer"
817 
818 TEXTCOMMAND(textcopy, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); textfocus->copyselectionto(b););
819 TEXTCOMMAND(textpaste, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); textfocus->insertallfrom(b););
820 TEXTCOMMAND(textmark, "i", (int *m),  // (1=mark, 2=unmark), return current mark setting if no args
821     if(*m) textfocus->mark(*m==1);
822     else intret(textfocus->region() ? 1 : 2);
823 );
824 TEXTCOMMAND(textselectall, "", (), textfocus->selectall(););
825 TEXTCOMMAND(textclear, "", (), textfocus->clear(););
826 TEXTCOMMAND(textcurrentline, "",  (), result(textfocus->currentline().text););
827 
828 TEXTCOMMAND(textexec, "i", (int *selected), // execute script commands from the buffer (0=all, 1=selected region only)
829     char *script = *selected ? textfocus->selectiontostring() : textfocus->tostring();
830     execute(script);
831     delete[] script;
832 );
833 
834 TEXTCOMMAND(textadd, "ss", (char *name, char *str), // loads into named editor if no file assigned and editor has been rendered
835 {
836     editor *e = NULL;
837     loopv(editors) if(!strcmp(editors[i]->name, name)) { e = editors[i]; break; }
838     if(e && e->rendered) e->insert(str);
839 });
840