Implement pager scrollback history buffer

For now, use big fixed-size buffer that is lost on resize
This commit is contained in:
Dominique Martinet 2018-09-15 15:37:44 +09:00
parent a4c2a9ef0d
commit d0104660c8
3 changed files with 89 additions and 5 deletions

View File

@ -173,11 +173,19 @@ typedef struct {
line_attrs_type *line_attrs;
} HistoryBufSegment;
typedef struct {
index_type bufsize;
Py_UCS4 *buffer;
index_type start, end;
index_type bufend;
} PagerHistoryBuf;
typedef struct {
PyObject_HEAD
index_type xnum, ynum, num_segments;
HistoryBufSegment* segments;
HistoryBufSegment *segments;
PagerHistoryBuf pagerhist;
Line *line;
index_type start_of_data, count;
} HistoryBuf;

View File

@ -74,6 +74,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
add_segment(self);
self->line = alloc_line();
self->line->xnum = xnum;
self->pagerhist.start = self->pagerhist.end = self->pagerhist.bufend = 0;
self->pagerhist.buffer = PyMem_RawMalloc(1024*1024*10);
self->pagerhist.bufsize = 1024*1024*10 / sizeof(Py_UCS4);
// abort if allocation failed?
}
return (PyObject*)self;
@ -88,6 +92,7 @@ dealloc(HistoryBuf* self) {
PyMem_Free(self->segments[i].line_attrs);
}
PyMem_Free(self->segments);
PyMem_Free(self->pagerhist.buffer);
Py_TYPE(self)->tp_free((PyObject*)self);
}
@ -132,12 +137,37 @@ historybuf_clear(HistoryBuf *self) {
self->start_of_data = 0;
}
static inline void
pagerhist_push(HistoryBuf *self) {
PagerHistoryBuf *ph = &self->pagerhist;
if (!ph->buffer) return;
Line l = {.xnum=self->xnum};
init_line(self, self->start_of_data, &l);
if (ph->start != ph->end && !l.continued) {
ph->buffer[ph->end++] = '\n';
}
if (ph->bufsize - ph->end < 1024) { ph->bufend = ph->end; ph->end = 0; }
ph->end += line_as_ansi(&l, ph->buffer + ph->end, 1024);
if (ph->bufend) {
#if NEXTLINE
/* something like wcsrchr would be more accurate, but is *slow* */
Py_UCS4 *newstart = (Py_UCS4 *)strchr((char *)ph->buffer + ph->end, '\n');
if (!newstart || newstart - ph->buffer > ph->bufend) ph->start = 0;
else ph->start = newstart - ph->buffer;
#else
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
#endif
}
}
static inline index_type
historybuf_push(HistoryBuf *self) {
index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line);
if (self->count == self->ynum) self->start_of_data = (self->start_of_data + 1) % self->ynum;
else self->count++;
if (self->count == self->ynum) {
pagerhist_push(self);
self->start_of_data = (self->start_of_data + 1) % self->ynum;
} else self->count++;
return idx;
}
@ -209,6 +239,48 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
static inline Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
static PyObject *
pagerhist_as_text(HistoryBuf *self, PyObject *callback) {
PagerHistoryBuf *ph = &self->pagerhist;
PyObject *ret = NULL, *t = NULL;
Py_UCS4 *buf = NULL;
if (!ph->buffer) Py_RETURN_NONE;
index_type num = (ph->bufend ? ph->bufend : ph->end) - ph->start;
buf = ph->buffer + ph->start;
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
if (t == NULL) goto end;
ret = PyObject_CallFunctionObjArgs(callback, t, NULL);
Py_DECREF(t);
if (ret == NULL) goto end;
Py_DECREF(ret);
if (ph->bufend) {
num = ph->end;
buf = ph->buffer;
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
if (t == NULL) goto end;
ret = PyObject_CallFunctionObjArgs(callback, t, NULL);
Py_DECREF(t);
if (ret == NULL) goto end;
Py_DECREF(ret);
}
Line l = {.xnum=self->xnum};
get_line(self, 0, &l);
if (!l.continued) {
t = PyUnicode_FromString("\n");
if (t == NULL) goto end;
ret = PyObject_CallFunctionObjArgs(callback, t, NULL);
Py_DECREF(t);
if (ret == NULL) goto end;
Py_DECREF(ret);
}
end:
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
static PyObject*
as_text(HistoryBuf *self, PyObject *args) {
Line l = {.xnum=self->xnum};
@ -238,6 +310,7 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
static PyMethodDef methods[] = {
METHOD(line, METH_O)
METHOD(as_ansi, METH_O)
METHODB(pagerhist_as_text, METH_O),
METHODB(as_text, METH_VARARGS),
METHOD(dirty_lines, METH_NOARGS)
METHOD(push, METH_VARARGS)

View File

@ -436,7 +436,7 @@ class Window:
self.screen.reset_callbacks()
self.screen = None
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False):
def as_text(self, as_ansi=False, add_history=False, add_pager_history=False, add_wrap_markers=False, alternate_screen=False):
lines = []
add_history = add_history and not self.screen.is_using_alternate_linebuf() and not alternate_screen
if alternate_screen:
@ -446,6 +446,9 @@ class Window:
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h = []
if add_pager_history:
# assert as_ansi and add_wrap_markers?
self.screen.historybuf.pagerhist_as_text(h.append)
self.screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
lines = h + lines
return ''.join(lines)
@ -461,7 +464,7 @@ class Window:
# actions {{{
def show_scrollback(self):
data = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = self.as_text(as_ansi=True, add_history=True, add_pager_history=True, add_wrap_markers=True)
data = data.replace('\r\n', '\n').replace('\r', '\n')
lines = data.count('\n')
input_line_number = (lines - (self.screen.lines - 1) - self.screen.scrolled_by)