summaryrefslogtreecommitdiff
path: root/src/engine/textedit.h
diff options
context:
space:
mode:
authorxolatile2025-07-16 23:07:43 +0200
committerxolatile2025-07-16 23:07:43 +0200
commit7256502afa0babe60fcafbd2888cd3e33c3f9b6b (patch)
tree8a8495662a69bdadc4b5d9152656b9f02a44d668 /src/engine/textedit.h
parentbc596ac9d4cdd00abf537b88d3c544be161330cc (diff)
downloadxolatile-badassbug-7256502afa0babe60fcafbd2888cd3e33c3f9b6b.tar.xz
xolatile-badassbug-7256502afa0babe60fcafbd2888cd3e33c3f9b6b.tar.zst
Source code, broken...
Diffstat (limited to 'src/engine/textedit.h')
-rw-r--r--src/engine/textedit.h770
1 files changed, 770 insertions, 0 deletions
diff --git a/src/engine/textedit.h b/src/engine/textedit.h
new file mode 100644
index 0000000..c273661
--- /dev/null
+++ b/src/engine/textedit.h
@@ -0,0 +1,770 @@
+
+struct editline
+{
+ enum { CHUNKSIZE = 256 };
+
+ char *text;
+ int len, maxlen;
+
+ editline() : text(NULL), len(0), maxlen(0) {}
+ editline(const char *init) : text(NULL), len(0), maxlen(0)
+ {
+ set(init);
+ }
+
+ bool empty() { return len <= 0; }
+
+ void clear()
+ {
+ DELETEA(text);
+ len = maxlen = 0;
+ }
+
+ bool grow(int total, const char *fmt = "", ...)
+ {
+ if(total + 1 <= maxlen) return false;
+ maxlen = (total + CHUNKSIZE) - total%CHUNKSIZE;
+ char *newtext = new char[maxlen];
+ if(fmt)
+ {
+ va_list args;
+ va_start(args, fmt);
+ vformatstring(newtext, fmt, args, maxlen);
+ va_end(args);
+ }
+ else newtext[0] = '\0';
+ DELETEA(text);
+ text = newtext;
+ return true;
+ }
+
+ void set(const char *str, int slen = -1)
+ {
+ if(slen < 0)
+ {
+ slen = strlen(str);
+ if(!grow(slen, "%s", str)) memcpy(text, str, slen + 1);
+ }
+ else
+ {
+ grow(slen);
+ memcpy(text, str, slen);
+ text[slen] = '\0';
+ }
+ len = slen;
+ }
+
+ void prepend(const char *str)
+ {
+ int slen = strlen(str);
+ if(!grow(slen + len, "%s%s", str, text ? text : ""))
+ {
+ memmove(&text[slen], text, len + 1);
+ memcpy(text, str, slen + 1);
+ }
+ len += slen;
+ }
+
+ void append(const char *str)
+ {
+ int slen = strlen(str);
+ if(!grow(len + slen, "%s%s", text ? text : "", str)) memcpy(&text[len], str, slen + 1);
+ len += slen;
+ }
+
+ bool read(stream *f, int chop = -1)
+ {
+ if(chop < 0) chop = INT_MAX; else chop++;
+ set("");
+ while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len))
+ {
+ len += strlen(&text[len]);
+ if(len > 0 && text[len-1] == '\n')
+ {
+ text[--len] = '\0';
+ return true;
+ }
+ if(len + 1 >= maxlen && len + 1 < chop) grow(len + CHUNKSIZE, "%s", text);
+ }
+ if(len + 1 >= chop)
+ {
+ char buf[CHUNKSIZE];
+ while(f->getline(buf, sizeof(buf)))
+ {
+ int blen = strlen(buf);
+ if(blen > 0 && buf[blen-1] == '\n') return true;
+ }
+ }
+ return len > 0;
+ }
+
+ void del(int start, int count)
+ {
+ if(!text) return;
+ if(start < 0) { count += start; start = 0; }
+ if(count <= 0 || start >= len) return;
+ if(start + count > len) count = len - start - 1;
+ memmove(&text[start], &text[start+count], len + 1 - (start + count));
+ len -= count;
+ }
+
+ void chop(int newlen)
+ {
+ if(!text) return;
+ len = clamp(newlen, 0, len);
+ text[len] = '\0';
+ }
+
+ void insert(char *str, int start, int count = 0)
+ {
+ if(count <= 0) count = strlen(str);
+ start = clamp(start, 0, len);
+ grow(len + count, "%s", text ? text : "");
+ memmove(&text[start + count], &text[start], len - start + 1);
+ memcpy(&text[start], str, count);
+ len += count;
+ }
+
+ void combinelines(vector<editline> &src)
+ {
+ if(src.empty()) set("");
+ else loopv(src)
+ {
+ if(i) append("\n");
+ if(!i) set(src[i].text, src[i].len);
+ else insert(src[i].text, len, src[i].len);
+ }
+ }
+};
+
+struct editor
+{
+ int mode; //editor mode - 1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes)
+ bool active, rendered;
+ const char *name;
+ const char *filename;
+
+ int cx, cy; // cursor position - ensured to be valid after a region() or currentline()
+ int mx, my; // selection mark, mx=-1 if following cursor - avoid direct access, instead use region()
+ int maxx, maxy; // maxy=-1 if unlimited lines, 1 if single line editor
+
+ int scrolly; // vertical scroll offset
+
+ bool linewrap;
+ int pixelwidth; // required for up/down/hit/draw/bounds
+ int pixelheight; // -1 for variable sized, i.e. from bounds()
+
+ vector<editline> lines; // MUST always contain at least one line!
+
+ editor(const char *name, int mode, const char *initval) :
+ mode(mode), active(true), rendered(false), name(newstring(name)), filename(NULL),
+ cx(0), cy(0), mx(-1), maxx(-1), maxy(-1), scrolly(0), linewrap(false), pixelwidth(-1), pixelheight(-1)
+ {
+ //printf("editor %08x '%s'\n", this, name);
+ lines.add().set(initval ? initval : "");
+ }
+
+ ~editor()
+ {
+ //printf("~editor %08x '%s'\n", this, name);
+ DELETEA(name);
+ DELETEA(filename);
+ clear(NULL);
+ }
+
+ void clear(const char *init = "")
+ {
+ cx = cy = 0;
+ mark(false);
+ loopv(lines) lines[i].clear();
+ lines.shrink(0);
+ if(init) lines.add().set(init);
+ }
+
+ void setfile(const char *fname)
+ {
+ DELETEA(filename);
+ if(fname) filename = newstring(fname);
+ }
+
+ void load()
+ {
+ if(!filename) return;
+ clear(NULL);
+ stream *file = openutf8file(filename, "r");
+ if(file)
+ {
+ while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy));
+ lines.pop().clear();
+ delete file;
+ }
+ if(lines.empty()) lines.add().set("");
+ }
+
+ void save()
+ {
+ if(!filename) return;
+ stream *file = openutf8file(filename, "w");
+ if(!file) return;
+ loopv(lines) file->putline(lines[i].text);
+ delete file;
+ }
+
+ void mark(bool enable)
+ {
+ mx = (enable) ? cx : -1;
+ my = cy;
+ }
+
+ void selectall()
+ {
+ mx = my = INT_MAX;
+ cx = cy = 0;
+ }
+
+ // constrain results to within buffer - s=start, e=end, return true if a selection range
+ // also ensures that cy is always within lines[] and cx is valid
+ bool region(int &sx, int &sy, int &ex, int &ey)
+ {
+ int n = lines.length();
+ ASSERT(n != 0);
+ if(cy < 0) cy = 0; else if(cy >= n) cy = n-1;
+ int len = lines[cy].len;
+ if(cx < 0) cx = 0; else if(cx > len) cx = len;
+ if(mx >= 0)
+ {
+ if(my < 0) my = 0; else if(my >= n) my = n-1;
+ len = lines[my].len;
+ if(mx > len) mx = len;
+ }
+ sx = (mx >= 0) ? mx : cx;
+ sy = (mx >= 0) ? my : cy;
+ ex = cx;
+ ey = cy;
+ if(sy > ey) { swap(sy, ey); swap(sx, ex); }
+ else if(sy==ey && sx > ex) swap(sx, ex);
+ return (sx != ex) || (sy != ey);
+ }
+
+ bool region() { int sx, sy, ex, ey; return region(sx, sy, ex, ey); }
+
+ // also ensures that cy is always within lines[] and cx is valid
+ editline &currentline()
+ {
+ int n = lines.length();
+ ASSERT(n != 0);
+ if(cy < 0) cy = 0; else if(cy >= n) cy = n-1;
+ if(cx < 0) cx = 0; else if(cx > lines[cy].len) cx = lines[cy].len;
+ return lines[cy];
+ }
+
+ void copyselectionto(editor *b)
+ {
+ if(b==this) return;
+
+ b->clear(NULL);
+ int sx, sy, ex, ey;
+ region(sx, sy, ex, ey);
+ loopi(1+ey-sy)
+ {
+ if(b->maxy != -1 && b->lines.length() >= b->maxy) break;
+ int y = sy+i;
+ char *line = lines[y].text;
+ int len = lines[y].len;
+ if(y == sy && y == ey)
+ {
+ line += sx;
+ len = ex - sx;
+ }
+ else if(y == sy) line += sx;
+ else if(y == ey) len = ex;
+ b->lines.add().set(line, len);
+ }
+ if(b->lines.empty()) b->lines.add().set("");
+ }
+
+ char *tostring()
+ {
+ int len = 0;
+ loopv(lines) len += lines[i].len + 1;
+ char *str = newstring(len);
+ int offset = 0;
+ loopv(lines)
+ {
+ editline &l = lines[i];
+ memcpy(&str[offset], l.text, l.len);
+ offset += l.len;
+ str[offset++] = '\n';
+ }
+ str[offset] = '\0';
+ return str;
+ }
+
+ char *selectiontostring()
+ {
+ vector<char> buf;
+ int sx, sy, ex, ey;
+ region(sx, sy, ex, ey);
+ loopi(1+ey-sy)
+ {
+ int y = sy+i;
+ char *line = lines[y].text;
+ int len = lines[y].len;
+ if(y == sy && y == ey)
+ {
+ line += sx;
+ len = ex - sx;
+ }
+ else if(y == sy) line += sx;
+ else if(y == ey) len = ex;
+ buf.put(line, len);
+ buf.add('\n');
+ }
+ buf.add('\0');
+ return newstring(buf.getbuf(), buf.length()-1);
+ }
+
+ void removelines(int start, int count)
+ {
+ loopi(count) lines[start+i].clear();
+ lines.remove(start, count);
+ }
+
+ bool del() // removes the current selection (if any)
+ {
+ int sx, sy, ex, ey;
+ if(!region(sx, sy, ex, ey))
+ {
+ mark(false);
+ return false;
+ }
+ if(sy == ey)
+ {
+ if(sx == 0 && ex == lines[ey].len) removelines(sy, 1);
+ else lines[sy].del(sx, ex - sx);
+ }
+ else
+ {
+ if(ey > sy+1) { removelines(sy+1, ey-(sy+1)); ey = sy+1; }
+ if(ex == lines[ey].len) removelines(ey, 1); else lines[ey].del(0, ex);
+ if(sx == 0) removelines(sy, 1); else lines[sy].del(sx, lines[sy].len - sx);
+ }
+ if(lines.empty()) lines.add().set("");
+ mark(false);
+ cx = sx;
+ cy = sy;
+ editline &current = currentline();
+ if(cx >= current.len && cy < lines.length() - 1)
+ {
+ current.append(lines[cy+1].text);
+ removelines(cy + 1, 1);
+ }
+ return true;
+ }
+
+ void insert(char ch)
+ {
+ del();
+ editline &current = currentline();
+ if(ch == '\n')
+ {
+ if(maxy == -1 || cy < maxy-1)
+ {
+ editline newline(&current.text[cx]);
+ current.chop(cx);
+ cy = min(lines.length(), cy+1);
+ lines.insert(cy, newline);
+ }
+ else current.chop(cx);
+ cx = 0;
+ }
+ else
+ {
+ int len = current.len;
+ if(maxx >= 0 && len > maxx-1) len = maxx-1;
+ if(cx <= len) current.insert(&ch, cx++, 1);
+ }
+ }
+
+ void insert(const char *s)
+ {
+ while(*s) insert(*s++);
+ }
+
+ void insertallfrom(editor *b)
+ {
+ if(b==this) return;
+
+ del();
+
+ if(b->lines.length() == 1 || maxy == 1)
+ {
+ editline &current = currentline();
+ char *str = b->lines[0].text;
+ int slen = b->lines[0].len;
+ if(maxx >= 0 && b->lines[0].len + cx > maxx) slen = maxx-cx;
+ if(slen > 0)
+ {
+ int len = current.len;
+ if(maxx >= 0 && slen + cx + len > maxx) len = max(0, maxx-(cx+slen));
+ current.insert(str, cx, slen);
+ cx += slen;
+ }
+ }
+ else
+ {
+ loopv(b->lines)
+ {
+ if(!i)
+ {
+ lines[cy++].append(b->lines[i].text);
+ }
+ else if(i >= b->lines.length())
+ {
+ cx = b->lines[i].len;
+ lines[cy].prepend(b->lines[i].text);
+ }
+ else if(maxy < 0 || lines.length() < maxy) lines.insert(cy++, editline(b->lines[i].text));
+ }
+ }
+ }
+
+ void key(int code)
+ {
+ switch(code)
+ {
+ case SDLK_UP:
+ if(linewrap)
+ {
+ int x, y;
+ char *str = currentline().text;
+ text_pos(str, cx+1, x, y, pixelwidth);
+ if(y > 0) { cx = text_visible(str, x, y-FONTH, pixelwidth); break; }
+ }
+ cy--;
+ break;
+ case SDLK_DOWN:
+ if(linewrap)
+ {
+ int x, y, width, height;
+ char *str = currentline().text;
+ text_pos(str, cx, x, y, pixelwidth);
+ text_bounds(str, width, height, pixelwidth);
+ y += FONTH;
+ if(y < height) { cx = text_visible(str, x, y, pixelwidth); break; }
+ }
+ cy++;
+ break;
+ case -4:
+ cy--;
+ break;
+ case -5:
+ cy++;
+ break;
+ case SDLK_PAGEUP:
+ cy-=pixelheight/FONTH;
+ break;
+ case SDLK_PAGEDOWN:
+ cy+=pixelheight/FONTH;
+ break;
+ case SDLK_HOME:
+ cx = cy = 0;
+ break;
+ case SDLK_END:
+ cx = cy = INT_MAX;
+ break;
+ case SDLK_LEFT:
+ cx--;
+ break;
+ case SDLK_RIGHT:
+ cx++;
+ break;
+ case SDLK_DELETE:
+ if(!del())
+ {
+ editline &current = currentline();
+ if(cx < current.len) current.del(cx, 1);
+ else if(cy < lines.length()-1)
+ { //combine with next line
+ current.append(lines[cy+1].text);
+ removelines(cy+1, 1);
+ }
+ }
+ break;
+ case SDLK_BACKSPACE:
+ if(!del())
+ {
+ editline &current = currentline();
+ if(cx > 0) current.del(--cx, 1);
+ else if(cy > 0)
+ { //combine with previous line
+ cx = lines[cy-1].len;
+ lines[cy-1].append(current.text);
+ removelines(cy--, 1);
+ }
+ }
+ break;
+ case SDLK_LSHIFT:
+ case SDLK_RSHIFT:
+ break;
+ case SDLK_RETURN:
+ insert('\n');
+ break;
+ case SDLK_TAB:
+ insert('\t');
+ break;
+ }
+ }
+
+ void input(const char *str, int len)
+ {
+ loopi(len) insert(str[i]);
+ }
+
+ void hit(int hitx, int hity, bool dragged)
+ {
+ int maxwidth = linewrap?pixelwidth:-1;
+ int h = 0;
+ for(int i = scrolly; i < lines.length(); i++)
+ {
+ int width, height;
+ text_bounds(lines[i].text, width, height, maxwidth);
+ if(h + height > pixelheight) break;
+
+ if(hity >= h && hity <= h+height)
+ {
+ int x = text_visible(lines[i].text, hitx, hity-h, maxwidth);
+ if(dragged) { mx = x; my = i; } else { cx = x; cy = i; };
+ break;
+ }
+ h+=height;
+ }
+ }
+
+ int limitscrolly()
+ {
+ int maxwidth = linewrap?pixelwidth:-1;
+ int slines = lines.length();
+ for(int ph = pixelheight; slines > 0 && ph > 0;)
+ {
+ int width, height;
+ text_bounds(lines[slines-1].text, width, height, maxwidth);
+ if(height > ph) break;
+ ph -= height;
+ slines--;
+ }
+ return slines;
+ }
+
+ void draw(int x, int y, int color, bool hit)
+ {
+ int maxwidth = linewrap?pixelwidth:-1;
+
+ int sx, sy, ex, ey;
+ bool selection = region(sx, sy, ex, ey);
+
+ // fix scrolly so that <cx, cy> is always on screen
+ if(cy < scrolly) scrolly = cy;
+ else
+ {
+ if(scrolly < 0) scrolly = 0;
+ int h = 0;
+ for(int i = cy; i >= scrolly; i--)
+ {
+ int width, height;
+ text_bounds(lines[i].text, width, height, maxwidth);
+ if(h + height > pixelheight) { scrolly = i+1; break; }
+ h += height;
+ }
+ }
+
+ if(selection)
+ {
+ // convert from cursor coords into pixel coords
+ int psx, psy, pex, pey;
+ text_pos(lines[sy].text, sx, psx, psy, maxwidth);
+ text_pos(lines[ey].text, ex, pex, pey, maxwidth);
+ int maxy = lines.length();
+ int h = 0;
+ for(int i = scrolly; i < maxy; i++)
+ {
+ int width, height;
+ text_bounds(lines[i].text, width, height, maxwidth);
+ if(h + height > pixelheight) { maxy = i; break; }
+ if(i == sy) psy += h;
+ if(i == ey) { pey += h; break; }
+ h += height;
+ }
+ maxy--;
+
+ if(ey >= scrolly && sy <= maxy)
+ {
+ // crop top/bottom within window
+ if(sy < scrolly) { sy = scrolly; psy = 0; psx = 0; }
+ if(ey > maxy) { ey = maxy; pey = pixelheight - FONTH; pex = pixelwidth; }
+
+ hudnotextureshader->set();
+ gle::colorub(0xA0, 0x80, 0x80);
+ gle::defvertex(2);
+ gle::begin(GL_QUADS);
+ if(psy == pey)
+ {
+ gle::attribf(x+psx, y+psy);
+ gle::attribf(x+pex, y+psy);
+ gle::attribf(x+pex, y+pey+FONTH);
+ gle::attribf(x+psx, y+pey+FONTH);
+ }
+ else
+ { gle::attribf(x+psx, y+psy);
+ gle::attribf(x+psx, y+psy+FONTH);
+ gle::attribf(x+pixelwidth, y+psy+FONTH);
+ gle::attribf(x+pixelwidth, y+psy);
+ if(pey-psy > FONTH)
+ {
+ gle::attribf(x, y+psy+FONTH);
+ gle::attribf(x+pixelwidth, y+psy+FONTH);
+ gle::attribf(x+pixelwidth, y+pey);
+ gle::attribf(x, y+pey);
+ }
+ gle::attribf(x, y+pey);
+ gle::attribf(x, y+pey+FONTH);
+ gle::attribf(x+pex, y+pey+FONTH);
+ gle::attribf(x+pex, y+pey);
+ }
+ gle::end();
+ hudshader->set();
+ }
+ }
+
+ int h = 0;
+ for(int i = scrolly; i < lines.length(); i++)
+ {
+ int width, height;
+ text_bounds(lines[i].text, width, height, maxwidth);
+ if(h + height > pixelheight) break;
+
+ draw_text(lines[i].text, x, y+h, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, hit&&(cy==i)?cx:-1, maxwidth);
+ if(linewrap && height > FONTH) // line wrap indicator
+ {
+ hudnotextureshader->set();
+ gle::colorub(0x80, 0xA0, 0x80);
+ gle::defvertex(2);
+ gle::begin(GL_TRIANGLE_STRIP);
+ gle::attribf(x, y+h+FONTH);
+ gle::attribf(x, y+h+height);
+ gle::attribf(x-FONTW/2, y+h+FONTH);
+ gle::attribf(x-FONTW/2, y+h+height);
+ gle::end();
+ hudshader->set();
+ }
+ h+=height;
+ }
+ }
+};
+
+// a 'stack' where the last is the current focused editor
+static vector <editor*> editors;
+
+static editor *currentfocus() { return editors.length() ? editors.last() : NULL; }
+
+static void readyeditors()
+{
+ loopv(editors) editors[i]->active = (editors[i]->mode==EDITORFOREVER);
+}
+
+static void flusheditors()
+{
+ loopvrev(editors) if(!editors[i]->active)
+ {
+ editor *e = editors.remove(i);
+ DELETEP(e);
+ }
+}
+
+static editor *useeditor(const char *name, int mode, bool focus, const char *initval = NULL)
+{
+ loopv(editors) if(strcmp(editors[i]->name, name) == 0)
+ {
+ editor *e = editors[i];
+ if(focus) { editors.add(e); editors.remove(i); } // re-position as last
+ e->active = true;
+ return e;
+ }
+ editor *e = new editor(name, mode, initval);
+ if(focus) editors.add(e); else editors.insert(0, e);
+ return e;
+}
+
+
+#define TEXTCOMMAND(f, s, d, body) ICOMMAND(f, s, d,\
+ editor *top = currentfocus();\
+ if(!top || identflags&IDF_OVERRIDDEN) return;\
+ body\
+)
+
+ICOMMAND(textlist, "", (), // @DEBUG return list of all the editors
+ vector<char> s;
+ loopv(editors)
+ {
+ if(i > 0) s.put(", ", 2);
+ s.put(editors[i]->name, strlen(editors[i]->name));
+ }
+ s.add('\0');
+ result(s.getbuf());
+);
+TEXTCOMMAND(textshow, "", (), // @DEBUG return the start of the buffer
+ editline line;
+ line.combinelines(top->lines);
+ result(line.text);
+ line.clear();
+);
+ICOMMAND(textfocus, "si", (char *name, int *mode), // focus on a (or create a persistent) specific editor, else returns current name
+ if(*name) useeditor(name, *mode<=0 ? EDITORFOREVER : *mode, true);
+ else if(editors.length() > 0) result(editors.last()->name);
+);
+TEXTCOMMAND(textprev, "", (), editors.insert(0, top); editors.pop();); // return to the previous editor
+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
+ if(*m) top->mode = *m;
+ else intret(top->mode);
+);
+TEXTCOMMAND(textsave, "s", (char *file), // saves the topmost (filename is optional)
+ if(*file) top->setfile(path(file, true));
+ top->save();
+);
+TEXTCOMMAND(textload, "s", (char *file), // loads into the topmost editor, returns filename if no args
+ if(*file)
+ {
+ top->setfile(path(file, true));
+ top->load();
+ }
+ else if(top->filename) result(top->filename);
+);
+TEXTCOMMAND(textinit, "sss", (char *name, char *file, char *initval), // loads into named editor if no file assigned and editor has been rendered
+{
+ editor *e = NULL;
+ loopv(editors) if(!strcmp(editors[i]->name, name)) { e = editors[i]; break; }
+ if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.length() == 1 && !strcmp(e->lines[0].text, initval))))
+ {
+ e->setfile(path(file, true));
+ e->load();
+ }
+});
+
+#define PASTEBUFFER "#pastebuffer"
+
+TEXTCOMMAND(textcopy, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); top->copyselectionto(b););
+TEXTCOMMAND(textpaste, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); top->insertallfrom(b););
+TEXTCOMMAND(textmark, "i", (int *m), // (1=mark, 2=unmark), return current mark setting if no args
+ if(*m) top->mark(*m==1);
+ else intret(top->region() ? 1 : 2);
+);
+TEXTCOMMAND(textselectall, "", (), top->selectall(););
+TEXTCOMMAND(textclear, "", (), top->clear(););
+TEXTCOMMAND(textcurrentline, "", (), result(top->currentline().text););
+
+TEXTCOMMAND(textexec, "i", (int *selected), // execute script commands from the buffer (0=all, 1=selected region only)
+ char *script = *selected ? top->selectiontostring() : top->tostring();
+ execute(script);
+ delete[] script;
+);
+