if (strcmp(ci->key, "doc:vmark-get") == 0) {
struct mark *m, *m2;
- m = do_vmark_first(dd->doc, ci->numeric);
- m2 = do_vmark_last(dd->doc, ci->numeric);
+ if (ci->extra == 2) {
+ m = m2 = NULL;
+ } else {
+ m = do_vmark_first(dd->doc, ci->numeric);
+ m2 = do_vmark_last(dd->doc, ci->numeric);
+ }
if (ci->extra == 1 && dd->point)
m2 = do_vmark_at_point(dd->doc, dd->point,
ci->numeric);
if (strcmp(ci->key, "Chr-h") == 0)
renderer = "hex";
+ if (strcmp(ci->key, "Chr-p") == 0)
+ renderer = "present";
if (strcmp(ci->key, "Chr-o") == 0) {
struct pane *p2 = call_pane("OtherPane", ci->focus, 0, NULL, 0);
key_add(docs_map, "Chr-f", &docs_open);
key_add(docs_map, "Chr-h", &docs_open);
+ key_add(docs_map, "Chr-p", &docs_open);
key_add(docs_map, "Return", &docs_open);
key_add(docs_map, "Chr-o", &docs_open);
key_add(docs_map, "Chr-q", &docs_bury);
{
struct mark *ret;
- if (view == MARK_POINT || view >= d->nviews || d->views[view].state != 1)
+ if (view == MARK_POINT || view >= d->nviews ||
+ (view != MARK_UNGROUPED && d->views[view].state != 1))
return NULL;
ret = calloc(sizeof(*ret), 1);
ret->viewnum = view;
/* Just use this, or nearby */
struct mark *vm2;
while ((vm2 = vmark_next(vm)) != NULL &&
- mark_same(d, vm, vm2))
+ mark_same(d, vm, m))
vm = vm2;
+ while (vm && vm->seq > m->seq && !mark_same(d, vm, m))
+ vm = vmark_prev(vm);
}
return vm;
}
/* doc_notify_change is slower than point_notify_change, but only
* requires a mark, not a point.
- * A second mark should only be given in the first mark is a point
+ * A second mark should only be given if the first mark is a point
*/
void doc_notify_change(struct doc *d, struct mark *m, struct mark *m2)
{
return key_handle(&ci);
}
+static inline int call_xy7(char *key, struct pane *focus, int numeric, int extra,
+ char *str, char *str2, int x, int y, struct mark *m, struct mark *m2)
+{
+ struct cmd_info ci = {0};
+
+ ci.key = key;
+ ci.focus = focus;
+ ci.numeric = numeric;
+ ci.extra = extra;
+ ci.str = str;
+ ci.str2 = str2;
+ ci.x = x;
+ ci.y = y;
+ ci.mark = m;
+ ci.mark2 = m2;
+ return key_handle(&ci);
+}
+
static inline int call7(char *key, struct pane *focus, int numeric, struct mark *m,
char *str, int extra, char *str2, struct mark *m2)
{
char *a = attr_get_str(d->attrs, attr, -1);
if (a)
return a;
- if (strcmp(attr, "default-renderer") == 0)
+ if (strcmp(attr, "default-renderer") == 0) {
+// if (t->fname && strcmp(t->fname + strlen(t->fname)-3, ".md") == 0)
+// return "present";
return "lines";
+ }
if (strcmp(attr, "filename") == 0)
return t->fname;
return NULL;
#include "core.h"
-char WelcomeText[] =
+static char WelcomeText[] =
+ ":H1:center,bg:green\n"
+ "\n"
+ "# heading\n"
+ "line\n"
"\n"
"Welcome to 'edlib' - the beginning of what one day might be an editor\n"
"\n"
- "Current functionality includes:\n"
+ "# Current functionality includes:\n"
" splitting and closing windows (C-x 0,1,2,3)\n"
" Resize current window (C-x },{,^)\n"
" Move among windows (C-x o,O or mouse click)\n"
editor_load_module(ed, "mode-emacs");
call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0);
+ memset(&ci, 0, sizeof(ci));
+ ci.home = ci.focus = global;
+ ci.key = "python-load";
+ ci.str = "python/render-present.py";
+ key_handle(&ci);
+
b = pane_attach(global, "tile", NULL, NULL);
if (b)
p = doc_from_text(b, "*Welcome*", WelcomeText);
ci.str = "python/test.py";
key_handle(&ci);
+ /* New window.. */
+ if (gtk) {
+ memset(&ci, 0, sizeof(ci));
+ ci.home = ci.focus = vroot;
+ ci.key = "display-pygtk";
+ cr.c = take_pane;
+ cr.p = NULL;
+ ci.comm2 = &cr.c;
+ key_handle(&ci);
+ root = pane_attach(cr.p, "input", NULL, NULL);
+ global = pane_attach(root, "messageline", NULL, NULL);
+ global = pane_attach(global, "global-keymap", NULL, NULL);
+ call5("global-set-keymap", global, 0, NULL, "mode-emacs", 0);
+ b = pane_attach(global, "tile", NULL, NULL);
+ p = doc_from_text(b, "*Welcome*", WelcomeText);
+ }
+
pane_refresh(&ed->root);
while (call3("event:run", vroot, 0, NULL) == 1)
;
Py_INCREF(Py_None);
return Py_None;
}
- if (rv < 0) {
+ if (rv == -1000) {
PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
- if (rv < 0) {
+ if (rv == -1000) {
PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
- if (rv < 0) {
+ if (rv == -1000) {
PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
- if (rv < 0) {
+ if (rv == -1000) {
PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
return NULL;
}
return Py_BuildValue("ii", x, y);
}
+static PyObject *Pane_render_attach(Pane *self, PyObject *args)
+{
+ char *type = NULL;
+ struct pane *p;
+ int ret = PyArg_ParseTuple(args, "s", &type);
+ if (ret <= 0)
+ return NULL;
+ p = render_attach(type, self->pane);
+ if (!p) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return Pane_Frompane(p);
+}
+
+static PyObject *Pane_damaged(Pane *self, PyObject *args)
+{
+ if (self->pane) {
+ pane_damaged(self->pane, DAMAGED_SIZE);
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
static PyObject *Pane_close(Pane *self)
{
struct pane *p = self->pane;
"Convert absolute co-orders to pane-relative"},
{"add_notify", (PyCFunction)Pane_add_notify, METH_VARARGS,
"Add notified for an event on some other pane"},
+ {"render_attach", (PyCFunction)Pane_render_attach, METH_VARARGS,
+ "Attach a renderer to a pane"},
+ {"damage", (PyCFunction)Pane_damaged, METH_VARARGS,
+ "Mark pane as damaged"},
{NULL}
};
{NULL} /* Sentinel */
};
+static PyObject *Pane_get_item(Pane *self, PyObject *key)
+{
+ char *k, *v;
+ if (!self->pane) {
+ PyErr_SetString(PyExc_TypeError, "Pane is NULL");
+ return NULL;
+ }
+ if (!PyString_Check(key)) {
+ PyErr_SetString(PyExc_TypeError, "Key must be a string");
+ return NULL;
+ }
+ k = PyString_AsString(key);
+ v = attr_get_str(self->pane->attrs, k, -1);
+ if (v)
+ return Py_BuildValue("s", v);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static int Pane_set_item(Pane *self, PyObject *key, PyObject *val)
+{
+ char *k, *v;
+ if (!self->pane) {
+ PyErr_SetString(PyExc_TypeError, "Pane is NULL");
+ return -1;
+ }
+ if (!PyString_Check(key)) {
+ PyErr_SetString(PyExc_TypeError, "Key must be a string");
+ return -1;
+ }
+ if (!PyString_Check(val)) {
+ PyErr_SetString(PyExc_TypeError, "value must be a string");
+ return -1;
+ }
+ k = PyString_AsString(key);
+ v = PyString_AsString(val);
+ attr_set_str(&self->pane->attrs, k, v, -1);
+ return 0;
+}
+
+static PyMappingMethods pane_mapping = {
+ .mp_length = NULL,
+ .mp_subscript = (binaryfunc)Pane_get_item,
+ .mp_ass_subscript = (objobjargproc)Pane_set_item,
+};
+
static PyTypeObject PaneType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
(reprfunc)pane_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
- 0, /*tp_as_mapping*/
+ &pane_mapping, /*tp_as_mapping*/
(hashfunc)pane_hash, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
return -1;
}
+static PyObject *Mark_getseq(Mark *m, void *x)
+{
+ if (m->mark == NULL) {
+ PyErr_SetString(PyExc_TypeError, "Mark is NULL");
+ return NULL;
+ }
+ return PyInt_FromLong(m->mark->seq);
+}
+
+static int Mark_nosetseq(Mark *m, PyObject *v, void *which)
+{
+ PyErr_SetString(PyExc_TypeError, "Cannot set mark seq num");
+ return -1;
+}
+
static PyObject *mark_getdata(Mark *m, void *x)
{
PyObject *ret;
PyObject *mark_compare(Mark *a, Mark *b, int op)
{
int ret = 0;
- switch(op) {
- case Py_LT: ret = mark_ordered(a->mark, b->mark); break;
- case Py_LE: ret = mark_ordered(a->mark, b->mark); break;
- case Py_GT: ret = mark_ordered(a->mark, b->mark); break;
- case Py_GE: ret = mark_ordered(a->mark, b->mark); break;
- case Py_EQ: ret = mark_ordered(a->mark, b->mark); break;
- case Py_NE: ret = mark_ordered(a->mark, b->mark); break;
- }
- return ret ? Py_True : Py_False;
+ PyObject *rv;
+ if ((PyObject*)a == Py_None)
+ ret = (op == Py_LT || op == Py_LE || op == Py_NE);
+ else if ((PyObject*)b == Py_None)
+ ret = (op == Py_GT || op == Py_GE || op == Py_EQ);
+ else if (PyObject_TypeCheck(a, &MarkType) == 0 ||
+ PyObject_TypeCheck(b, &MarkType) == 0) {
+ PyErr_SetString(PyExc_TypeError, "Mark compared with non-Mark");
+ return NULL;
+ } else
+ switch(op) {
+ case Py_LT: ret = mark_ordered(a->mark, b->mark); break;
+ case Py_LE: ret = !mark_ordered(b->mark, a->mark); break;
+ case Py_GT: ret = mark_ordered(b->mark, a->mark); break;
+ case Py_GE: ret = !mark_ordered(a->mark, b->mark); break;
+ case Py_EQ: ret = !mark_ordered(a->mark, b->mark) && !mark_ordered(b->mark, a->mark); break;
+ case Py_NE: ret = mark_ordered(a->mark, b->mark) || mark_ordered(b->mark, a->mark); break;
+ }
+ rv = ret ? Py_True : Py_False;
+ Py_INCREF(rv);
+ return rv;
}
static PyGetSetDef mark_getseters[] = {
{"viewnum",
(getter)mark_getview, (setter)mark_nosetview,
"Index for view list", NULL},
+ {"seq",
+ (getter)Mark_getseq, (setter)Mark_nosetseq,
+ "Seq number of mark", NULL},
{"data",
(getter)mark_getdata, (setter)mark_setdata,
"Application data", NULL},
return Py_None;
}
-static PyObject *Mark_dup(Mark *self)
+static PyObject *Mark_dup(Mark *self, PyObject *args)
{
struct mark *new;
+ int ret;
+ int no_type = 0;
if (!self->mark) {
PyErr_SetString(PyExc_TypeError, "Mark is NULL");
return NULL;
}
- new = mark_dup(self->mark, 0);
+ ret = PyArg_ParseTuple(args, "|i", &no_type);
+ if (ret <= 0)
+ return NULL;
+ new = mark_dup(self->mark, no_type);
if (new)
return Mark_Frommark(new);
Py_INCREF(Py_None);
return Py_None;
}
+static PyObject *Mark_unlink(Mark *self)
+{
+ if (!self->mark) {
+ PyErr_SetString(PyExc_TypeError, "Mark is NULL");
+ return NULL;
+ }
+ mark_free(self->mark);
+ self->mark = NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
static PyMethodDef mark_methods[] = {
{"to_mark", (PyCFunction)Mark_to_mark, METH_VARARGS,
"Move one mark to another"},
"previous vmark"},
{"next_any", (PyCFunction)Mark_next_any, METH_NOARGS,
"next any_mark"},
- {"dup", (PyCFunction)Mark_dup, METH_NOARGS,
- "duplicate a mark, preserving type"},
+ {"dup", (PyCFunction)Mark_dup, METH_VARARGS,
+ "duplicate a mark, preserving type .. or not if '1' is given"},
+ {"unlink", (PyCFunction)Mark_unlink, METH_NOARGS,
+ "unlink mark and discard it"},
{NULL}
};
Py_INCREF(Py_None);
return Py_None;
}
- if (rv < 0) {
+ if (rv == -1000) {
PyErr_SetString(Edlib_CommandFailed, ci.str ? ci.str : "Command Failed");
return NULL;
}
}
for (i = 1; i < argc; i++) {
a = PyTuple_GetItem(args, i);
- if (Py_TYPE(a) == &PaneType) {
+ if (PyObject_TypeCheck(a, &PaneType)) {
if (ci->focus == NULL)
ci->focus = ((Pane*)a)->pane;
else if (ci->home == NULL)
char *message;
struct pane *line;
int height; /* height of line */
- int ascent; /* how for down to baseline */
+ int ascent; /* how far down to baseline */
+ int hidden;
};
static void pane_str(struct pane *p, char *s, char *attr, int x, int y)
{
struct mlinfo *mli = ci->home->data;
+ if (strcmp(ci->key, "Window:borderless") == 0) {
+ if (ci->numeric > 0)
+ mli->hidden = 1;
+ else
+ mli->hidden = 0;
+ pane_damaged(ci->home, DAMAGED_SIZE);
+ return 0; /* Allow other handlers */
+ }
+
if (strcmp(ci->key, "Message") == 0) {
if (ci->extra == 0 || mli->message == NULL) {
free(mli->message);
mli->ascent = cr.i;
}
if (ci->home == mli->line) {
- pane_resize(ci->home, 0, ci->home->parent->h - mli->height,
- ci->home->parent->w, mli->height);
+ if (mli->hidden)
+ pane_resize(ci->home, 0, ci->home->parent->h,
+ ci->home->parent->w, mli->height);
+ else
+ pane_resize(ci->home, 0, ci->home->parent->h - mli->height,
+ ci->home->parent->w, mli->height);
pane_clear(mli->line, "bg:cyan");
if (mli->message)
pane_str(mli->line, mli->message, "bold,fg:red,bg:cyan",
0, 0 + mli->ascent);
} else {
pane_resize(ci->home, 0, 0, ci->home->parent->w,
- ci->home->parent->h - mli->height);
+ ci->home->parent->h - (mli->hidden ? 0 : mli->height));
}
return 1;
}
mli->message = NULL;
mli->height = 0;
+ mli->hidden = 0;
ret = pane_register(p, 0, &messageline_handle, mli, NULL);
mli->line = pane_register(p, 1, &messageline_handle, mli, NULL);
pane_focus(ci->focus);
struct view_data {
int border;
+ int old_border;
int border_width, border_height;
int line_height;
int ascent;
vd = malloc(sizeof(*vd));
vd->border = border;
+ vd->old_border = border;
vd->line_height = -1;
vd->border_width = vd->border_height = -1;
p = pane_register(par, 0, &view_handle, vd, NULL);
return call3(key, p, num, NULL);
}
+DEF_CMD(view_borderless)
+{
+ struct pane *p = ci->home;
+ struct view_data *vd = p->data;
+
+ if (ci->numeric > 0) {
+ vd->border = 0;
+ } else {
+ vd->border = vd->old_border;
+ }
+ pane_damaged(p, DAMAGED_SIZE);
+ return 0; /* Allow other handlers */
+}
+
void edlib_init(struct editor *ed)
{
view_map = key_alloc();
key_add(view_map, "Click-1", &view_click);
key_add(view_map, "Press-1", &view_click);
+ key_add(view_map, "Window:borderless", &view_borderless);
key_add(ed->commands, "attach-view", &view_attach);
}
def handle(self, key, **a):
+ if key == "Window:fullscreen":
+ if a['numeric'] > 0:
+ self.fullscreen()
+ else:
+ self.unfullscreen()
+
if key == "Close":
self.pane.close()
# FIXME close the window??
if key == "pane-clear":
f = a["focus"]
if "str2" in a:
+ print "CLEAR", a['str2']
fg, bg = self.get_colours(a["str2"])
else:
- fg, bg = self.get_colours("")
+ fg, bg = self.get_colours("bg:white")
pm = self.get_pixmap(f)
self.do_clear(pm, bg)
return True
if key == "text-size":
- fd = self.extract_font(a["str2"])
+ fd = self.extract_font(a["str2"], a['extra'])
ctx = self.text.get_pango_context()
metric = ctx.get_metrics(fd)
self.text.modify_font(fd)
self.gc.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("blue")))
if not self.bg:
self.bg = t.window.new_gc()
+ cmap = t.get_colormap()
+ self.bg.set_foreground(cmap.alloc_color(gtk.gdk.color_parse("white")))
(x,y) = a["xy"]
f = a["focus"]
attr = a['str2']
else:
attr = ''
- fd = self.extract_font(attr)
+ fd = self.extract_font(attr, a['extra'])
ctx = self.text.get_pango_context()
self.text.modify_font(fd)
layout = self.text.create_pango_layout(a["str"])
metric = ctx.get_metrics(fd)
ascent = metric.get_ascent() / pango.SCALE
ink,(lx,ly,width,height) = layout.get_pixel_extents()
- self.bg.set_foreground(bg)
- pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height)
- pm.draw_layout(self.gc, x, y-ascent, layout, fg)
+ if bg:
+ self.bg.set_foreground(bg)
+ pm.draw_rectangle(self.bg, True, x+lx, y-ascent+ly, width, height)
+ pm.draw_layout(self.gc, x, y-ascent, layout, fg, bg)
if a['numeric'] >= 0:
cx,cy,cw,ch = layout.index_to_pos(a["numeric"])
if cw <= 0:
if f.parent.focus != f:
extra = False
f = f.parent
+ print x,cx,y,cy,cw,ch,extra,a['numeric'],len(a['str'])
if extra:
pm.draw_rectangle(self.gc, True, x+cx, y-ascent+cy,
cw, ch);
s = unicode(a["str"][c:], "utf-8")
l2 = pango.Layout(ctx)
l2.set_text(s[0])
- pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg)
+ pm.draw_layout(self.gc, x+cx, y-ascent+cy, l2, bg, fg)
+ else:
+ pm.draw_rectangle(self.gc, False, x+cx, y-ascent+cy,
+ cw-1, ch-1);
+ return True
+
+ if key == "image-display":
+ # 'str' is a file name of an image
+ # xy is location for top/left
+ # numeric/extra are max width/height
+ # I should use a pane-per-image but I'm in a rush
+ fl = a['str']
+ f = a['focus']
+ w = a['numeric']
+ h = a['extra']
+ if h == 0:
+ h = w
+ (x,y) = a['xy']
+ try:
+ pb = gtk.gdk.pixbuf_new_from_file(fl)
+ except:
+ pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
+ pb.fill(0xff000000)
+ if pb.get_width() * h > pb.get_height() * w:
+ # image is wider than space, so reduce height
+ h2 = pb.get_height() * w / pb.get_width()
+ y += (h-h2)/2
+ h = h2
+ else:
+ # image is taller than space
+ w2 = pb.get_width() * h / pb.get_height()
+ x += (w-w2)/2
+ w = w2
+ scale = pb.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
+ pm = self.get_pixmap(f)
+ pm.draw_pixbuf(self.gc, scale, 0, 0, x, y)
return True
if key == "Notify:Close":
styles=["oblique","italic","bold","small-caps"]
- def extract_font(self, attrs):
+ def extract_font(self, attrs, scale):
"Return a pango.FontDescription"
family="mono"
style=""
size=10
+ if scale <= 10:
+ scale = 1000
for word in attrs.split(','):
if word in self.styles:
style += " " + word
+ elif len(word) and word[0].isdigit():
+ size = word
elif word == "large":
- size = 14
+ size = 40
+ elif word == "small":
+ size = 8.9
elif word[0:7] == "family:":
family = word[7:]
- return pango.FontDescription(family+' '+style+' '+str(size))
+ fd = pango.FontDescription(family+' '+style+' '+str(size))
+ if scale != 1000:
+ fd.set_size(fd.get_size() * scale / 1000)
+ return fd
def get_colours(self, attrs):
"Return a foreground and a background colour"
- fg = "black"
- bg = "white"
+ fg = None
+ bg = None
inv = False
for word in attrs.split(','):
if word[0:3] == "fg:":
if word == "inverse":
inv = True
cmap = self.text.get_colormap()
- fgc = cmap.alloc_color(gtk.gdk.color_parse(fg))
- bgc = cmap.alloc_color(gtk.gdk.color_parse(bg))
if inv:
- return (bgc, fgc)
+ fg,bg = bg,fg
+ if fg is None:
+ fg = "white"
+ if bg is None:
+ bg = "black"
+ if fg is None:
+ fg = "black"
+
+ if fg:
+ try:
+ c = gtk.gdk.color_parse(fg)
+ except:
+ c = gtk.gdk.color_parse("black")
+ fgc = cmap.alloc_color(c)
+ else:
+ fgc = None
+ if bg:
+ try:
+ c = gtk.gdk.color_parse(bg)
+ except:
+ c = gtk.gdk.color_parse("white")
+ bgc = cmap.alloc_color(c)
else:
- return (fgc, bgc)
+ bgc = None
+ return fgc, bgc
def get_pixmap(self, p):
if p in self.panes:
--- /dev/null
+# -*- coding: utf-8 -*-
+# Copyright Neil Brown ©2015 <neil@brown.name>
+# May be distributed under terms of GPLv2 - see file:COPYING
+
+# edlib rendering module for slide presentation.
+# this reads lines from document, interprets as soemthing vaguely like
+# markdown or restructuredtext, and displays a page at a time with formatting.
+#
+# Parsing is very line-oriented.
+# - a line starting ":ptype:" defines attributes for given paragraph type
+# - a line starting "#" is a heading, "##" is a level-2 heading (H1 H2 ...)
+# - a line starting ">" is indented (I)
+# - a line starting "-" is a list item. (L1)
+# - a line starting " -" after a list item is a second-level list item (L2)
+# - a line starting " " after a list item is more text in that list item
+# - a line starting " " elsewhere is a code fragment (C)
+# - a line starting "!" treats rest of line a name of file containing an image
+# - other lines are text (P)
+#
+# A level-1 heading (H1) starts a new page. attribute lines immediately preceding
+# it are part of the page and apply only to the page
+# Attribute lines before the first page apply to whole document but can be over-ridden
+# in a page.
+#
+# Within text *word* becomes bold (b), _word_ is italic (i) `word` is monospaced (m)
+#
+# attributes can be defined for paragraph types (H1,h2,I,L1,L2,C,P) and text types
+# (b,i,m). Possible attributes for para or text are:
+# normal oblique italic bold small-caps large NN family:fontfamily fg:colour bg:colour inverse
+# Possible attributes for para only:
+# left:size right:size centre space-after:size space-before:size
+#
+# All the above can also be given for "default" and apply before other defaults and
+# global/local setting. "default" can also be given:
+# xscale:size yscale:size
+#
+# All sizes are in "points". A "point" is either the height of the window divided by
+# the yscale size, or the width divided by xscale size, whichever is smaller.
+#
+# global defaults are:
+
+default_attrs = "normal 10 family:sans fg:black bg:white left:5 space-after:1 space-before:1 xscale:10 yscale:40"
+
+import re
+
+def take(name, place, args, default=None):
+ if name in args:
+ place.append(args[name])
+ else:
+ place.append(default)
+ return 1
+
+class PresenterPane(edlib.Pane):
+ def __init__(self, focus):
+ edlib.Pane.__init__(self, focus, self.handle)
+ self.globals = {}
+ self.pageview = focus.call("doc:add-view") - 1
+ self.attrview = focus.call("doc:add-view") - 1
+ self.borderless = False
+
+ def marks_same(self, m1, m2):
+ if isinstance(m1, edlib.Mark) and isinstance(m1, edlib.Mark):
+ return 1 == self.call("doc:mark-same", m1, m2);
+ return None
+
+ def first_page(self):
+ m = []
+ self.call("doc:vmark-get", self.pageview, lambda key, **a: take('mark', m, a))
+ return m[0]
+
+ def first_line(self):
+ m = []
+ self.call("doc:vmark-get", self.attrview, lambda key, **a: take('mark', m, a))
+ return m[0]
+
+ def prev_page(self, m):
+ m2 = []
+ self.call("doc:vmark-get", self.pageview, 3, m, lambda key, **a: take('mark2', m2, a))
+ return m2[0]
+
+ def prev_line(self, m):
+ m2 = []
+ self.call("doc:vmark-get", self.attrview, 3, m, lambda key, **a: take('mark2', m2, a))
+ return m2[0]
+
+ def get_line_at(self, m):
+ # call render-line at m
+ line = []
+ self.parent.call_filter("render-line", m, -1, lambda key, **a: take('str', line, a, ''))
+ return line[0]
+
+ def get_line_before(self, m):
+ m2 = m.dup(1)
+ ret = self.parent.call_filter("render-line-prev", m2, 1)
+ if ret <= 0:
+ m2.unlink()
+ return None
+ l = self.get_line_at(m2)
+ m2.unlink()
+ return l
+
+ def print_line_at(self, m):
+ x = m.dup(1)
+ l = self.get_line_at(x)
+ x.unlink()
+ return l[0:-1]
+
+ def find_page(self, start, sof = False):
+ # Assumming 'm' is either start-of-file (if 'sof') or the start of
+ # a page, find the start of the next page. Return an untyped mark
+ # or None if there is no next page.
+ # A page starts with some ":attr:" lines and then a "# " line.
+ # If not 'sof' we first need to skip over the start of 'this' page.
+ skipping = not sof
+ m = start.dup(1)
+ maybe = None
+ globals = None
+ if sof:
+ globals = {}
+ extra = {}
+ while True:
+ if not maybe and not skipping:
+ maybe = m.dup()
+ l = self.get_line_at(m)
+ if not l:
+ # End of document
+ if maybe:
+ maybe.unlink()
+ m.unlink()
+ return None
+ if l[0] == ':':
+ # attribute - doesn't advance possible start
+ if globals is not None:
+ mt = re.match("^:([^: ]+):(.*)", l.strip('\n'))
+ if mt:
+ extra[mt.group(1)] = mt.group(2)
+ elif l[0:2] == "# ":
+ # level-1 heading. This is it if not skipping
+ if skipping:
+ skipping = False
+ else:
+ m.unlink()
+ if globals is not None:
+ self.globals = globals
+ if 'background' in globals:
+ self['background'] = globals['background']
+ if 'scale' in globals:
+ self['scale'] = globals['scale']
+ self.call("render-lines:redraw")
+ return maybe
+ else:
+ # not part of start-of-page
+ skipping = False
+ if maybe:
+ maybe.unlink()
+ maybe = None
+ if globals is not None:
+ globals.update(extra)
+ extra = {}
+
+ def check_start(self, start):
+ # This was the start of a page, but might not be any more. Must check.
+ # Following lines must be ":attr:" or "# ".
+ # Preceeding line, if any must not be ":attr:"
+ m = start.dup(1)
+ l = self.get_line_at(m)
+ while l and l[0] == ':':
+ l = self.get_line_at(m)
+ m.unlink()
+ if not l or l[0:2] != '# ':
+ # No appropriate heading
+ return False
+
+ m = start.dup(1)
+ l = self.get_line_before(m)
+ m.unlink()
+ if l and l[0] == ':':
+ return False
+ return True
+
+ def find_pages(self, m):
+ # I need to find the start of the page that contains 'm' - though if m is before
+ # the first page, then I want the first page.
+ # I need to know that the 'next' page after than really is the start of the following
+ # page, or is None iff this is the last page.
+ # Each page mark has a 'valid' attribute which confirms it is at start of page,
+ # and a 'next-valid' attribute which confirms next page is valid. These are clear
+ # when nearby text changes.
+ # The PresenterPane has a 'first_valid' flag is the first page mark is valid.
+ # it is like 'next-valid'.
+ # If these check out, answer is easy. If they don't we need to revalidate anything
+ # that doesn't match and either set a flag or delete a mark or add a mark. Then
+ # try again.
+ pm = None
+ while not pm:
+ pm = self.prev_page(m)
+ if not pm:
+ # could be in pre-amble
+ pm = self.first_page()
+ if not pm:
+ # no marks at all. Create mark-at-start and find page
+ pma=[]
+ self.call("doc:vmark-get", self.pageview, 2, lambda key,**a: take('mark2', pma, a))
+ pm = pma[0]
+ first = self.find_page(pm, True)
+ if first:
+ # Good, there is a page.
+ pm.to_mark(first)
+ self.first_valid = True
+ pm['valid'] = 'yes'
+ first.unlink()
+ pm = None
+ continue
+ # no pages at all
+ break
+ if not self.first_valid:
+ # We have a first page, but it might not be valid
+ pma=[]
+ self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a))
+ t = pma[0]
+ first = self.find_page(t, True)
+ t.unlink()
+ if not first:
+ # no pages any more
+ pass
+ elif self.marks_same(pm, first):
+ # Oh good!
+ self.first_valid = True
+ pm['valid'] = 'yes'
+ first.unlink()
+ elif first < pm:
+ pm.to_mark(first)
+ self.first_valid = True
+ pm['valid'] = 'yes'
+ # but having moved,..
+ pm['next-valid'] = 'no'
+ first.unlink()
+ else:
+ # .first_page() is before real first, just delete it
+ pm.unlink()
+ first.unlink()
+ pm = None
+ continue
+ # So this first page is valid, but is after the target.
+ # Fall through to checking next-page
+ elif pm['valid'] != 'yes':
+ # We have a previous page but need to validate it
+ if self.check_start(pm):
+ pm['valid'] = 'yes'
+ else:
+ if pm.prev():
+ pm.prev()['next-valid'] = 'no'
+ else:
+ self.first_valid = False
+ pm.unlink()
+ pm = None
+ continue
+
+ # pm is the start of this page, just need to check next
+ if pm['next-valid'] != 'yes':
+ next = self.find_page(pm)
+ if not next:
+ # this is last page
+ while pm.next():
+ pm.next().unlink()
+ pm['next-valid'] = 'yes'
+ # all good now
+ elif pm.next() is None:
+ n = pm.dup()
+ n.to_mark(next)
+ next.unlink()
+ pm['next-valid'] = 'yes'
+ else:
+ if self.marks_same(next, pm.next()):
+ pm['next-valid'] = 'yes'
+ elif next <= pm.next():
+ pm.next().to_mark(next)
+ else:
+ pm.next().unlink()
+ next.unlink()
+ pm = None
+
+ if not self.first_valid:
+ # need to update globals
+ pma=[]
+ self.call("doc:vmark-get", -2, 2, lambda key,**a: take('mark2', pma, a))
+ t = pma[0]
+ first = self.find_page(t, True)
+ if first:
+ self.first_valid = True
+ t.unlink()
+
+ #x = self.first_page()
+ #while x:
+ # print "PAGE:", x.seq, self.print_line_at(x)
+ # x = x.next()
+ #print "RETURN", pm.seq
+ return pm
+
+ # We maintain marks at the start of every line in the current page
+ # These have tags storing attributes extracted from the line.
+ # - If there are marks not in the current page, discard them.
+ # - If there are no marks, create them
+ # - get an attribute by searching back for marks of the right type
+ # If one has no type, validate it
+ #
+ def clean_lines(self, page):
+ next = page.next()
+ first = self.first_line()
+ while first and ((first < page and not self.marks_same(first,page)) or
+ (next and first > next and not self.marks_same(first, next))):
+ # first is outside this page
+ first.unlink()
+ first = self.first_line()
+
+ def annotate(self, mark, line):
+ m = re.match("^:([^: ]+):(.*)", line.strip('\n'))
+ if m:
+ mark['type'] = 'attr:'+m.group(1)
+ mark['value'] = m.group(2)
+ else:
+ mark['type'] = 'text'
+ mark['value'] = '-None-'
+
+ def mark_lines(self, page):
+ first = self.first_line()
+ if first:
+ return
+ # there are currently no lines
+ next = page.next()
+ la=[]
+ self.call("doc:vmark-get", self.attrview, 2, lambda key,**a: take('mark2', la, a))
+ line = la[0]
+
+ while not next or (line < next and not self.marks_same(line, next)):
+ # There is a line there that we care about - unless EOF
+ this = line.dup()
+ l = self.get_line_at(line)
+ if not l:
+ break
+ self.annotate(this, l)
+ line.unlink()
+
+ def get_local_attr(self, m, attr):
+ t = 'attr:' + attr
+ l = self.prev_line(m)
+ while l:
+ if l['type'] == 'unknown':
+ # Ouch - need to re-evaluate. Just assume still at start-of-line
+ self.annotate(l, self.print_line_at(l))
+ pass
+ if l['type'] == t:
+ return l['value']
+ l = l.prev()
+ return None
+
+ para_ptn = ['# ', '## ', '> ', '- ', ' ', '!', None, '']
+ para_type = ['H1', 'H2', 'I', 'L1', 'C', 'IM', 'BL', 'P']
+ defaults = {
+ 'H1': 'center,large,family:serif',
+ 'H2': 'right:20,fg:red,bold',
+ 'I': 'left:5,family:Mufferaw',
+ 'C': 'family:mono',
+ 'L1': 'fg:blue,left:40,family:sans',
+ 'P': 'left:30,family:sans,space-below:20',
+ 'BL': '3',
+ }
+
+ def handle(self, key, **a):
+ if key == "render-line-prev":
+ # Go to start of page
+ here = a['mark']
+ start = self.find_pages(here)
+ if not start:
+ return -2
+ if start > here:
+ start = here
+
+ if self.marks_same(start, here) and a['numeric'] == 1:
+ return -2
+ here.to_mark(start)
+ return 1
+
+ if key == "render-line":
+ here = a['mark']
+ cb = a['comm2']
+ page = self.find_pages(here)
+ if not page:
+ # No pages at all
+ cb("callback", self)
+ return 1
+
+ if here < page:
+ here.to_mark(page)
+
+ self.clean_lines(page)
+ self.mark_lines(page)
+
+ end = page.next()
+
+ line = None
+ while end is None or here < end:
+ lines = []
+ self.parent.call_filter("render-line", here,a['numeric'],
+ lambda key2, **aa: take('str', lines, aa))
+ if len(lines) == 0 or lines[0] is None:
+ line = None
+ break
+ line = lines[0]
+ if line[0] == ':':
+ #skip attributes
+ continue
+ line = line.strip("\n")
+ break
+
+ if line is None:
+ cb("callback", self)
+ else:
+ mode = 'P'
+ for n in range(len(self.para_ptn)):
+ prefix = self.para_ptn[n]
+ if prefix == None and line == "":
+ mode = self.para_type[n]
+ break
+ if prefix is not None and prefix == line[0:len(prefix)]:
+ mode = self.para_type[n]
+ line = line[len(prefix):]
+ break
+ v = self.get_local_attr(here, mode)
+ if not v and mode in self.globals:
+ v = self.globals[mode]
+ if not v and mode in self.defaults:
+ v = self.defaults[mode]
+ if not v:
+ v = ""
+
+ if mode == 'IM':
+ cb("callback", self, "<image:"+line+",width=200,height=100>")
+ return 1
+
+ line = re.sub("\*([A-Za-z0-9][^*<]*)\*", "<italic>\\1</>", line)
+ line = re.sub("`([A-Za-z0-9][^*<]*)`", "<family:mono>\\1</>", line)
+ line = "<"+v+">"+ line + "</>"
+ line += '\n'
+ if end and (here > end or self.marks_same(here,end)):
+ line += '\f'
+ cb("callback", self, line)
+ return 1
+
+ if key == "Notify:Replace":
+ m = a['mark']
+ # A change has happened at 'm'. The following page might not
+ # be valid, and the previous may not be valid or have next-valid.
+ # If no previous, self.first_valid may not be.
+ page = self.prev_page(m)
+ if not page:
+ # m is before first page
+ page = self.first_page()
+ if page:
+ self.first_valid = False
+ page['valid'] = 'no'
+ # attributes probably changed so...
+ self.call("render-lines:redraw")
+ else:
+ page['valid'] = 'no'
+ page['next-valid'] = 'no'
+ page = page.next()
+ if page:
+ page['valid'] = 'no'
+
+ l = self.prev_line(m)
+ if l:
+ if l['type'] and l['type'][0:5] == "attr:":
+ self.call("render-lines:redraw")
+ l['type'] = 'unknown'
+ l = l.next()
+ if l:
+ l['type'] = 'unknown'
+ return 1
+
+ if key == "Close":
+ # destroy all marks
+ self.release()
+
+ if key == "Clone":
+ # Need to create a new PresenterPane I guess, then recurse on children
+ pass
+
+ if key == "M-Chr-f":
+ if self.borderless:
+ self.call("Window:borderless", -1)
+ self.call("Window:fullscreen", -1)
+ self.borderless = False
+ else:
+ self.call("Window:borderless", 1)
+ self.call("Window:fullscreen", 1)
+ self.borderless = True
+ return 1
+
+ if key == "Next":
+ p = a['mark']
+ page = self.find_pages(p)
+ if a['numeric'] < 0:
+ page = page.prev()
+ else:
+ page = page.next()
+ if page is not None:
+ p.to_mark(page)
+ self.call("Move-View-Pos", page)
+ a['focus'].damage(1)
+ return 1
+ if key == "Prior":
+ p = a['mark']
+ page = self.find_pages(p)
+ if a['numeric'] < 0:
+ page = page.next()
+ else:
+ page = page.prev()
+ if page is not None:
+ p.to_mark(page)
+ self.call("Move-View-Pos", page)
+ a['focus'].damage(1)
+ return 1
+
+ return None
+
+def present_attach(key, focus, comm2, **a):
+ p = PresenterPane(focus)
+ p['render-wrap'] = 'no'
+ p['background'] = 'color:yellow'
+
+ p.call("Request:Notify:Replace")
+ p = p.render_attach("lines")
+ comm2('callback', p)
+ return 1
+
+editor.call("global-set-command", pane, "render-present-attach", present_attach)
* The returned line can contain attribute markings as <attr,attr>.
* </> is used to pop most recent attributes. << is used to include a literal '<'.
* Lines generally contains UTF-8. Control character '\n' is end of line and
- * '\t' tabs 1-8 spaces. Other control characters should be rendered as
+ * '\t' tabs 1-<8 spaces. Other control characters should be rendered as
* e.g. <fg:red>^X</> - in particular, nul must not appear in the line.
*
* We currently assume a constant-width font 1x1.
* the slow way if the target point wasn't found.
*/
+#define _GNU_SOURCE for-asprintf
#include <unistd.h>
#include <stdlib.h>
#include <wchar.h>
#include <string.h>
-
+#include <stdio.h>
#define MARK_DATA_PTR char
#include "core.h"
#include "misc.h"
#define CURS 2
static int draw_some(struct pane *p, int *x, int y, char *start, char **endp,
- char *attr, int margin, int cursorpos, int cursx)
+ char *attr, int margin, int cursorpos, int cursx, int scale)
{
/* draw some text from 'start' for length 'len' into p[x,y].
* Update 'x' and 'startp' past what as drawn.
*/
int len = *endp - start;
char *str = strndup(start, len);
- struct call_return cr;
+ struct call_return cr = {0};
int max;
int ret = WRAP;
int rmargin = p->w - margin;
}
cr.c = text_size_callback;
- call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+ /* FIXME check error - python might crash */
+ call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
max = cr.i;
if (max == 0 && ret == CURS) {
/* must already have CURS position. */
ret = WRAP;
rmargin = p->w - margin;
- call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+ call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
max = cr.i;
}
if (max < len) {
str[max] = 0;
- call_comm7("text-size", p, rmargin - *x, NULL, str, 0, attr, &cr.c);
+ call_comm7("text-size", p, rmargin - *x, NULL, str, scale, attr, &cr.c);
}
if (y >= 0) {
if (cursorpos >= 0 && cursorpos <= len)
- call_xy("text-display", p, cursorpos, str, attr, *x, y);
+ call_xy7("text-display", p, cursorpos, scale, str, attr, *x, y, NULL, NULL);
else
- call_xy("text-display", p, -1, str, attr, *x, y);
+ call_xy7("text-display", p, -1, scale, str, attr, *x, y, NULL, NULL);
}
free(str);
*x += cr.x;
return ret;
}
-static void update_line_height_attr(struct pane *p, int *h, int *a, char *attr)
+static void update_line_height_attr(struct pane *p, int *h, int *a, int *w, char *attr, char *str, int scale)
{
struct call_return cr;
cr.c = text_size_callback;
- call_comm7("text-size", p, -1, NULL, "M", 0, attr, &cr.c);
+ call_comm7("text-size", p, -1, NULL, str, scale, attr, &cr.c);
if (cr.y > *h)
*h = cr.y;
if (cr.i2 > *a)
*a = cr.i2;
+ if (w)
+ *w += cr.x;
}
-static void update_line_height(struct pane *p, int *h, int *a, char *line)
+static void update_line_height(struct pane *p, int *h, int *a, int *w, int *center, char *line,
+ int scale)
{
struct buf attr;
+ char *tst = line;
+ char *l;
+ int above = 0, below = 0;
+ int attr_found = 0;
buf_init(&attr);
- update_line_height_attr(p, h, a, "");
+ buf_append(&attr, ',');
while (*line) {
char c = *line++;
char *st = line;
if (c != '<' || *line == '<')
continue;
+ if (line-1 > tst) {
+ l = strndup(tst, line-1-tst);
+ update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale);
+ free(l);
+ }
while (*line && line[-1] != '>')
line += 1;
+ tst = line;
if (st[0] != '/') {
+ char *c;
buf_concat_len(&attr, st, line-st);
attr.b[attr.len-1] = ',';
buf_append(&attr, ',');
- update_line_height_attr(p, h, a, buf_final(&attr));
+ if (center && strstr(buf_final(&attr), ",center,"))
+ *center = 1;
+ if (center && (c=strstr(buf_final(&attr), ",left:")) != NULL) {
+ *center = atoi(c+6) * scale/1000;
+ }
+ if (center && (c=strstr(buf_final(&attr), ",right:")) != NULL) {
+ *center = - atoi(c+7) * scale/1000;
+ }
+ if ((c=strstr(buf_final(&attr), ",space-above:")) != NULL) {
+ above = atoi(c+13) * scale/1000;
+ }
+ if ((c=strstr(buf_final(&attr), ",space-below:")) != NULL) {
+ below = atoi(c+13) * scale/1000;
+ }
+ attr_found = 1;
+ update_line_height_attr(p, h, a, w, buf_final(&attr), "", scale);
} else {
/* strip back to ",," */
if (attr.len > 0)
attr.len -= 1;
}
}
+ if (line[-1] == '\n')
+ line -= 1;
+ if (line > tst || !attr_found) {
+ l = strndup(tst, line-tst);
+ update_line_height_attr(p, h, a, w, buf_final(&attr), l, scale);
+ free(l);
+ }
+ *h += above + below;
+ *a += above;
free(buf_final(&attr));
}
+static void render_image(struct pane *p, char *line, int *yp, int dodraw, int scale)
+{
+ char *cp, *e;
+ char *fname = NULL;
+ int width = p->w/2, height = p->h/2;
+ int y;
+
+ while (*line == '<')
+ line ++;
+ while (line) {
+ e = strchr(line, ',');
+ if (!e)
+ e = strchr(line, '>');
+ if (strncmp(line, "image:", 6) == 0) {
+ cp = line + 6;
+ if (e)
+ fname = strndup(cp, e-cp);
+ else
+ fname = strdup(cp);
+ } else if (strncmp(line, "width:", 6) == 0) {
+ width = atoi(line+6);
+ width = width * scale / 1000;
+ } else if (strncmp(line, "height:", 7) == 0) {
+ height = atoi(line+7);
+ height = height * scale / 1000;
+ }
+ if (e)
+ e++;
+ line = e;
+ }
+ y = *yp;
+ *yp = y + height;
+ if (!fname)
+ return;
+ if (!dodraw) {
+ free(fname);
+ return;
+ }
+ call_xy7("image-display", p, width, height, fname, NULL, (p->w - width)/2, y,
+ NULL, NULL);
+}
+
/* render a line, with attributes and wrapping. Report line offset where
* cursor point cx,cy is passed. -1 if never seen.
*/
-static void render_line(struct pane *p, char *line, int *yp, int dodraw,
+static void render_line(struct pane *p, char *line, int *yp, int dodraw, int scale,
int *cxp, int *cyp, int *offsetp)
{
int x = 0;
int ascent = -1;
int mwidth = -1;
int ret = 0;
+ int twidth = 0;
+ int center = 0;
+
+ /* Temporary hack (I hope) */
+ if (strncmp(line, "<image", 6) == 0) {
+ render_image(p, line, yp, dodraw, scale);
+ if (cxp) *cxp = -1;
+ if (cyp) *cyp = -1;
+ if (offsetp) *offsetp = -1;
+ return;
+ }
- update_line_height(p, &line_height, &ascent, line);
+ update_line_height(p, &line_height, &ascent, &twidth, ¢er, line, scale);
if (prefix) {
char *s = prefix + strlen(prefix);
- update_line_height_attr(p, &line_height, &ascent, "bold");
- draw_some(p, &x, dodraw?y+ascent:-1, prefix, &s, "bold", 0, -1, -1);
+ update_line_height_attr(p, &line_height, &ascent, NULL, "bold", prefix, scale);
+ draw_some(p, &x, dodraw?y+ascent:-1, prefix, &s, "bold", 0, -1, -1, scale);
}
rl->prefix_len = x;
+ if (center == 1)
+ x += (p->w - x - twidth)/2;
+ if (center > 1)
+ x += center;
+ if (center < 0)
+ x = p->w - x - twidth + center;
rl->line_height = line_height;
-
buf_init(&attr);
buf_append(&attr, ' '); attr.len = 0;
if (cxp && cyp && offsetp) {
mwidth = cr.x;
}
- if (ret == WRAP) {
+ if (ret == WRAP && wrap) {
char buf[2], *b;
strcpy(buf, "\\");
b = buf+1;
x = p->w - mwidth;
draw_some(p, &x, dodraw?y+ascent:-1, buf, &b, "underline,fg:blue",
- 0, -1, -1);
+ 0, -1, -1, scale);
x = 0;
y += line_height;
}
+ if (ret == WRAP && !wrap) {
+ while (*line && *line != '\n' && *line != '<')
+ line += 1;
+ start = line;
+ }
ch = *line;
(line-start) * mwidth > p->w - x) {
ret = draw_some(p, &x, dodraw?y+ascent:-1, start,
&line,
- buf_final(&attr), mwidth, CP, CX);
+ buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale);
start = line;
}
continue;
}
ret = draw_some(p, &x, dodraw?y+ascent:-1, start, &line,
- buf_final(&attr), mwidth, CP, CX);
+ buf_final(&attr), wrap ? mwidth : 0, CP, CX, scale);
+ if (!wrap && ret == WRAP && line == start)
+ ret = 0;
start = line;
if (ret)
continue;
if (ch == '<') {
line += 1;
if (*line == '<') {
- if (offset == start - line_start)
+ if (offset >= 0 && offset == start - line_start)
offset += 1;
start = line;
line += 1;
buf[2] = 0;
b = buf+2;
buf_concat(&attr, ",underline,fg:red");
+ if (ch == '\f') buf[0] = 0;
ret = draw_some(p, &x, dodraw?y+ascent:-1, buf, &b,
buf_final(&attr),
- mwidth*2, CP, CX);
+ wrap ? mwidth*2 : 0, CP, CX, scale);
attr.len = l;
start = line;
}
if (!*line && (line > start || offset == start - line_start)) {
/* Some more to draw */
draw_some(p, &x, dodraw?y+ascent:-1, start, &line,
- buf_final(&attr), mwidth, offset - (start - line_start), cx);
+ buf_final(&attr), wrap ? mwidth : 0, offset - (start - line_start), cx, scale);
}
if (y + line_height < cy ||
(y <= cy && x <= cx))
return NULL;
}
/* if n>0 we can fail because start-of-file was found before
- * and newline. In that case ret == -2, and we return NULL.
+ * any newline. In that case ret == -2, and we return NULL.
*/
if (found)
*found = (ret != -1);
return len;
}
+static int get_scale(struct pane *p)
+{
+ char *sc = pane_attr_get(p, "scale");
+ int x, y;
+ int xscale, yscale, scale;
+
+ if (!sc)
+ return 1000;
+
+ if (sscanf(sc, "x:%d,y:%d", &x, &y) != 2)
+ return 1000;
+
+ /* 'scale' is pixels per point times 1000.
+ * ":scale:x" is points across pane, so scale = p->w/x*1000
+ */
+ xscale = 1000 * p->w / x;
+ yscale = 1000 * p->h / y;
+ scale = (xscale < yscale) ? xscale: yscale;
+ if (scale < 10)
+ scale = 1000;
+ return scale;
+}
+
static void find_lines(struct mark *pm, struct pane *p)
{
struct rl_data *rl = p->data;
struct mark *top, *bot;
struct mark *m;
struct mark *start, *end;
+ int x;
int y = 0;
int offset;
int found_start = 0, found_end = 0;
int lines_above = 0, lines_below = 0;
+ int scale = get_scale(p);
- if (pm->viewnum != MARK_POINT)
- return;
+// if (pm->viewnum != MARK_POINT)
+// return;
top = vmark_first(p, rl->typenum);
bot = vmark_last(p, rl->typenum);
m = vmark_next(start);
end = m;
- if (start->mdata) {
- int x;
- x = -1; lines_above = -1; y = 0;
- render_line(p, start->mdata, &y, 0, &x, &lines_above, &offset);
- lines_below = y - lines_above;
- }
- y = 1;
- /* We have start/end of the focus line, and its height */
+ x = -1; lines_above = -1; y = 0;
+ render_line(p, start->mdata ?: "", &y, 0, scale, &x, &lines_above, &offset);
+ //lines_below = y - lines_above;
+ lines_above = lines_below = 0;
+
+ /* We have start/end of the focus line, and its height
+ * Rendering just that "line" uses a height of 'y', of which
+ * 'lines_above' is above the cursor, and 'lines_below' is below.
+ */
if (bot && !mark_ordered_or_same_pane(p, bot, start))
/* already before 'bot', so will never "cross over" bot, so
* ignore 'bot'
if (top && !mark_ordered_or_same_pane(p, end, top))
top = NULL;
- rl->skip_lines = 0;
- while (!((found_start && found_end) || y >= p->h - rl->header_lines)) {
- if (!found_start) {
+ while ((!found_start || !found_end) && y < p->h - rl->header_lines) {
+ if (!found_start && lines_above == 0) {
/* step backwards moving start */
- if (lines_above > 0) {
- lines_above -= 1;
- y += 1;
+ m = call_render_line_prev(p, mark_dup(start, 0),
+ 1, &rl->top_sol);
+ if (!m) {
+ /* no text before 'start' */
+ found_start = 1;
} else {
- m = call_render_line_prev(p, mark_dup(start, 0),
- 1, &rl->top_sol);
- if (!m) {
- /* no text before 'start' */
- found_start = 1;
- } else {
- int h = 0;
- start = m;
- if (!start->mdata)
- call_render_line(p, start);
- render_line(p, start->mdata, &h, 0,
+ int h = 0;
+ start = m;
+ if (!start->mdata)
+ call_render_line(p, start);
+ if (start->mdata)
+ render_line(p, start->mdata, &h, 0, scale,
NULL, NULL, NULL);
- if (h) {
- lines_above = h - 1;
- y += 1;
- } else
- found_start = 1;
- }
- if (bot && mark_ordered(start, bot))
- found_end = 1;
+ if (h) {
+ lines_above = h;
+ } else
+ found_start = 1;
}
+ if (bot && mark_ordered(start, bot))
+ found_end = 1;
}
- if (!found_end) {
+ if (!found_end && lines_below == 0) {
/* step forwards */
- if (lines_below > 0) {
- lines_below -= 1;
- y += 1;
- } else {
- if (!end->mdata)
- call_render_line(p, end);
- if (!end->mdata)
+ if (!end->mdata)
+ call_render_line(p, end);
+ if (!end->mdata)
+ found_end = 1;
+ else {
+ int h = 0;
+ render_line(p, end->mdata, &h, 0, scale,
+ NULL, NULL, NULL);
+ if (end->mdata[strlen(end->mdata)-1] == '\f')
+ found_end = 1;
+ end = vmark_next(end);
+ ASSERT(end != NULL);
+ if (h) {
+ lines_below = h;
+ } else
found_end = 1;
- else {
- int h = 0;
- render_line(p, end->mdata, &h, 0,
- NULL, NULL, NULL);
- end = vmark_next(end);
- ASSERT(end != NULL);
- if (h) {
- lines_below = h - 1;
- y += 1;
- } else
- found_end = 1;
- }
- if (top && mark_ordered(top, end))
- found_start = 1;
}
+ if (top && mark_ordered(top, end))
+ found_start = 1;
+ }
+ if (lines_above > 0 && lines_below > 0) {
+ int consume = (lines_above > lines_below ? lines_below:lines_above) * 2;
+ if (consume > (p->h - rl->header_lines) - y)
+ consume = (p->h - rl->header_lines) - y;
+ if (lines_above > lines_below) {
+ lines_above -= consume - (consume/2);
+ lines_below -= consume/2;
+ } else {
+ lines_below -= consume - (consume/2);
+ lines_above -= consume/2;
+ }
+ y += consume;
+ /* We have just consumed all of one of lines_{above,below}
+ * so they are no longer both > 0 */
+ }
+ if (found_end && lines_above) {
+ int consume = p->h - rl->header_lines - y;
+ if (consume > lines_above)
+ consume = lines_above;
+ lines_above -= consume;
+ y += consume;
+ }
+ if (found_start && lines_below) {
+ int consume = p->h - rl->header_lines - y;
+ if (consume > lines_below)
+ consume = lines_below;
+ lines_below -= consume;
+ y += consume;
}
}
rl->skip_lines = lines_above;
+ if (start->seq > end->seq)
+ end = start;
/* Now discard any marks outside start-end */
while ((m = vmark_prev(start)) != NULL) {
free(m->mdata);
struct mark *m, *m2;
int restarted = 0;
char *hdr;
+ char *bg;
+ int scale = get_scale(p);
hdr = pane_attr_get(p, "heading");
if (hdr && !*hdr)
hdr = NULL;
restart:
- pane_clear(p, NULL);
- y = 0;
+ bg = pane_attr_get(p, "background");
+ if (bg && strncmp(bg, "color:", 6) == 0) {
+ char *a;
+ asprintf(&a, "bg:%s", bg+6);
+ pane_clear(p, a);
+ free(a);
+ } else
+ pane_clear(p, NULL);
if (hdr) {
rl->header_lines = 0;
- render_line(p, hdr, &y, 1, NULL, NULL, NULL);
+ render_line(p, hdr, &y, 1, scale, NULL, NULL, NULL);
rl->header_lines = y;
}
y -= rl->skip_lines;
int len = call_render_line_to_point(p, pm,
m);
rl->cursor_line = y;
- render_line(p, m->mdata ?: "", &y, 1, &p->cx, &p->cy, &len);
+ render_line(p, m->mdata ?: "", &y, 1, scale, &p->cx, &p->cy, &len);
if (p->cy < 0)
p->cx = -1;
if (!rl->do_wrap && p->cy >= 0 && p->cx < rl->prefix_len) {
}
}
} else
- render_line(p, m->mdata?:"", &y, 1, NULL, NULL, NULL);
+ render_line(p, m->mdata?:"", &y, 1, scale, NULL, NULL, NULL);
+ if (m->mdata && m->mdata[strlen(m->mdata)-1]=='\f')
+ break;
if (!m2)
break;
m = m2;
struct rl_data *rl = p->data;
struct mark *top;
int pagesize = rl->line_height;
+ int scale = get_scale(p);
top = vmark_first(p, rl->typenum);
if (!top)
call_render_line(p, top);
if (top->mdata == NULL)
break;
- render_line(p, top->mdata, &y, 0, NULL, NULL, NULL);
+ render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL);
rl->skip_lines = y;
}
} else {
call_render_line(p, top);
if (top->mdata == NULL)
break;
- render_line(p, top->mdata, &y, 0, NULL, NULL, NULL);
+ render_line(p, top->mdata, &y, 0, scale, NULL, NULL, NULL);
if (rl->skip_lines + rpt < y) {
rl->skip_lines += rpt;
break;
int y = rl->header_lines - rl->skip_lines;
int found = 0;
int cihx, cihy;
+ int scale = get_scale(p);
render_lines_other_move_func(ci);
cihy = y;
while (y <= cihy && m && m->mdata) {
int cx = cihx, cy = cihy, o = -1;
- render_line(p, m->mdata, &y, 0, &cx, &cy, &o);
+ render_line(p, m->mdata, &y, 0, scale, &cx, &cy, &o);
if (o >= 0) {
struct mark *m2 = call_render_line_offset(p, m, o);
if (m2) {
bot = vmark_last(p, rl->typenum);
if (top && bot &&
mark_ordered(top, pm) &&
- mark_ordered(pm, bot))
+ mark_ordered(pm, bot) && !mark_same_pane(p, pm, bot, NULL))
/* pos already displayed */
return 1;
find_lines(pm, ci->home);
struct cmd_info ci2 = {0};
int target_x, target_y;
int o = -1;
+ int scale = get_scale(p);
rl->ignore_point = 0;
pane_damaged(p, DAMAGED_CONTENT);
return 1;
}
- render_line(p, start->mdata, &y, 0, &target_x, &target_y, &o);
+ render_line(p, start->mdata, &y, 0, scale, &target_x, &target_y, &o);
/* 'o' is the distance from start-of-line of the target */
if (o >= 0) {
struct mark *m2 = call_render_line_offset(
free(m->mdata);
m->mdata = NULL;
}
+ pane_damaged(p, DAMAGED_SIZE);
return 1;
}