# HG changeset patch # User Bram Moolenaar # Date 1368621592 -7200 # Node ID a84f21892563be88839b4026bafba77f9e9a1fec # Parent 57d8b1be2de532f9031f23f90f86b6cf0435789b updated for version 7.3.947 Problem: Python: No iterator for vim.list and vim.bufferlist. Solution: Add the iterators. Also fix name of FunctionType. Add tests for vim.buffers. (ZyX) diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -214,6 +214,7 @@ vim.buffers *python-buffers* :py b = vim.buffers[i] # Indexing (read-only) :py b in vim.buffers # Membership test :py n = len(vim.buffers) # Number of elements + :py for b in vim.buffers: # Iterating over buffer list < vim.windows *python-windows* A sequence object providing access to the list of vim windows. The diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -390,8 +390,6 @@ static char_u *get_lval __ARGS((char_u * static void clear_lval __ARGS((lval_T *lp)); static void set_var_lval __ARGS((lval_T *lp, char_u *endp, typval_T *rettv, int copy, char_u *op)); static int tv_op __ARGS((typval_T *tv1, typval_T *tv2, char_u *op)); -static void list_add_watch __ARGS((list_T *l, listwatch_T *lw)); -static void list_rem_watch __ARGS((list_T *l, listwatch_T *lwrem)); static void list_fix_watch __ARGS((list_T *l, listitem_T *item)); static void ex_unletlock __ARGS((exarg_T *eap, char_u *argstart, int deep)); static int do_unlet_var __ARGS((lval_T *lp, char_u *name_end, int forceit)); @@ -3106,7 +3104,7 @@ tv_op(tv1, tv2, op) /* * Add a watcher to a list. */ - static void + void list_add_watch(l, lw) list_T *l; listwatch_T *lw; @@ -3119,7 +3117,7 @@ list_add_watch(l, lw) * Remove a watcher from a list. * No warning when it isn't found... */ - static void + void list_rem_watch(l, lwrem) list_T *l; listwatch_T *lwrem; diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -531,66 +531,62 @@ static struct PyMethodDef VimMethods[] = }; /* - * Buffer list object - Implementation + * Generic iterator object */ -static PyTypeObject BufMapType; +static PyTypeObject IterType; + +typedef PyObject *(*nextfun)(void **); +typedef void (*destructorfun)(void *); + +/* Main purpose of this object is removing the need for do python initialization + * (i.e. PyType_Ready and setting type attributes) for a big bunch of objects. + */ typedef struct { PyObject_HEAD -} BufMapObject; - - static PyInt -BufMapLength(PyObject *self UNUSED) + void *cur; + nextfun next; + destructorfun destruct; +} IterObject; + + static PyObject * +IterNew(void *start, destructorfun destruct, nextfun next) { - buf_T *b = firstbuf; - PyInt n = 0; - - while (b) - { - ++n; - b = b->b_next; - } - - return n; + IterObject *self; + + self = PyObject_NEW(IterObject, &IterType); + self->cur = start; + self->next = next; + self->destruct = destruct; + + return (PyObject *)(self); +} + + static void +IterDestructor(PyObject *self) +{ + IterObject *this = (IterObject *)(self); + + this->destruct(this->cur); + + DESTRUCTOR_FINISH(self); } static PyObject * -BufMapItem(PyObject *self UNUSED, PyObject *keyObject) +IterNext(PyObject *self) { - buf_T *b; - int bnr; - -#if PY_MAJOR_VERSION < 3 - if (PyInt_Check(keyObject)) - bnr = PyInt_AsLong(keyObject); - else -#endif - if (PyLong_Check(keyObject)) - bnr = PyLong_AsLong(keyObject); - else - { - PyErr_SetString(PyExc_ValueError, _("key must be integer")); - return NULL; - } - - b = buflist_findnr(bnr); - - if (b) - return BufferNew(b); - else - { - PyErr_SetString(PyExc_KeyError, _("no such buffer")); - return NULL; - } + IterObject *this = (IterObject *)(self); + + return this->next(&this->cur); } -static PyMappingMethods BufMapAsMapping = { - (lenfunc) BufMapLength, - (binaryfunc) BufMapItem, - (objobjargproc) 0, -}; + static PyObject * +IterIter(PyObject *self) +{ + return self; +} typedef struct pylinkedlist_S { struct pylinkedlist_S *pll_next; @@ -990,6 +986,55 @@ ListSlice(PyObject *self, Py_ssize_t fir return list; } +typedef struct +{ + listwatch_T lw; + list_T *list; +} listiterinfo_T; + + static void +ListIterDestruct(listiterinfo_T *lii) +{ + list_rem_watch(lii->list, &lii->lw); + PyMem_Free(lii); +} + + static PyObject * +ListIterNext(listiterinfo_T **lii) +{ + PyObject *r; + + if (!((*lii)->lw.lw_item)) + return NULL; + + if (!(r = ConvertToPyObject(&((*lii)->lw.lw_item->li_tv)))) + return NULL; + + (*lii)->lw.lw_item = (*lii)->lw.lw_item->li_next; + + return r; +} + + static PyObject * +ListIter(PyObject *self) +{ + listiterinfo_T *lii; + list_T *l = ((ListObject *) (self))->list; + + if (!(lii = PyMem_New(listiterinfo_T, 1))) + { + PyErr_NoMemory(); + return NULL; + } + + list_add_watch(l, &lii->lw); + lii->lw.lw_item = l->lv_first; + lii->list = l; + + return IterNew(lii, + (destructorfun) ListIterDestruct, (nextfun) ListIterNext); +} + static int ListAssItem(PyObject *self, Py_ssize_t index, PyObject *obj) { @@ -2869,6 +2914,116 @@ static struct PyMethodDef BufferMethods[ { NULL, NULL, 0, NULL } }; +/* + * Buffer list object - Implementation + */ + +static PyTypeObject BufMapType; + +typedef struct +{ + PyObject_HEAD +} BufMapObject; + + static PyInt +BufMapLength(PyObject *self UNUSED) +{ + buf_T *b = firstbuf; + PyInt n = 0; + + while (b) + { + ++n; + b = b->b_next; + } + + return n; +} + + static PyObject * +BufMapItem(PyObject *self UNUSED, PyObject *keyObject) +{ + buf_T *b; + int bnr; + +#if PY_MAJOR_VERSION < 3 + if (PyInt_Check(keyObject)) + bnr = PyInt_AsLong(keyObject); + else +#endif + if (PyLong_Check(keyObject)) + bnr = PyLong_AsLong(keyObject); + else + { + PyErr_SetString(PyExc_ValueError, _("key must be integer")); + return NULL; + } + + b = buflist_findnr(bnr); + + if (b) + return BufferNew(b); + else + { + PyErr_SetString(PyExc_KeyError, _("no such buffer")); + return NULL; + } +} + + static void +BufMapIterDestruct(PyObject *buffer) +{ + /* Iteration was stopped before all buffers were processed */ + if (buffer) + { + Py_DECREF(buffer); + } +} + + static PyObject * +BufMapIterNext(PyObject **buffer) +{ + PyObject *next; + PyObject *r; + + if (!*buffer) + return NULL; + + r = *buffer; + + if (CheckBuffer((BufferObject *)(r))) + { + *buffer = NULL; + return NULL; + } + + if (!((BufferObject *)(r))->buf->b_next) + next = NULL; + else if (!(next = BufferNew(((BufferObject *)(r))->buf->b_next))) + return NULL; + *buffer = next; + /* Do not increment reference: we no longer hold it (decref), but whoever on + * other side will hold (incref). Decref+incref = nothing. + */ + return r; +} + + static PyObject * +BufMapIter(PyObject *self UNUSED) +{ + PyObject *buffer; + + buffer = BufferNew(firstbuf); + return IterNew(buffer, + (destructorfun) BufMapIterDestruct, (nextfun) BufMapIterNext); +} + +static PyMappingMethods BufMapAsMapping = { + (lenfunc) BufMapLength, + (binaryfunc) BufMapItem, + (objobjargproc) 0, +}; + /* Current items object */ @@ -3383,6 +3538,14 @@ init_structs(void) OutputType.tp_setattr = OutputSetattr; #endif + vim_memset(&IterType, 0, sizeof(IterType)); + IterType.tp_name = "vim.iter"; + IterType.tp_basicsize = sizeof(IterObject); + IterType.tp_flags = Py_TPFLAGS_DEFAULT; + IterType.tp_doc = "generic iterator object"; + IterType.tp_iter = IterIter; + IterType.tp_iternext = IterNext; + vim_memset(&BufferType, 0, sizeof(BufferType)); BufferType.tp_name = "vim.buffer"; BufferType.tp_basicsize = sizeof(BufferType); @@ -3426,6 +3589,7 @@ init_structs(void) BufMapType.tp_basicsize = sizeof(BufMapObject); BufMapType.tp_as_mapping = &BufMapAsMapping; BufMapType.tp_flags = Py_TPFLAGS_DEFAULT; + BufMapType.tp_iter = BufMapIter; BufferType.tp_doc = "vim buffer list"; vim_memset(&WinListType, 0, sizeof(WinListType)); @@ -3492,6 +3656,7 @@ init_structs(void) ListType.tp_flags = Py_TPFLAGS_DEFAULT; ListType.tp_doc = "list pushing modifications to vim structure"; ListType.tp_methods = ListMethods; + ListType.tp_iter = ListIter; #if PY_MAJOR_VERSION >= 3 ListType.tp_getattro = ListGetattro; ListType.tp_setattro = ListSetattro; @@ -3501,7 +3666,7 @@ init_structs(void) #endif vim_memset(&FunctionType, 0, sizeof(FunctionType)); - FunctionType.tp_name = "vim.list"; + FunctionType.tp_name = "vim.function"; FunctionType.tp_basicsize = sizeof(FunctionObject); FunctionType.tp_dealloc = FunctionDestructor; FunctionType.tp_call = FunctionCall; diff --git a/src/if_python.c b/src/if_python.c --- a/src/if_python.c +++ b/src/if_python.c @@ -1219,6 +1219,7 @@ PythonMod_Init(void) static char *(argv[2]) = {"/must>not&exist/foo", NULL}; /* Fixups... */ + PyType_Ready(&IterType); PyType_Ready(&BufferType); PyType_Ready(&RangeType); PyType_Ready(&WindowType); diff --git a/src/if_python3.c b/src/if_python3.c --- a/src/if_python3.c +++ b/src/if_python3.c @@ -1519,6 +1519,7 @@ Py3Init_vim(void) /* The special value is removed from sys.path in Python3_Init(). */ static wchar_t *(argv[2]) = {L"/must>not&exist/foo", NULL}; + PyType_Ready(&IterType); PyType_Ready(&BufferType); PyType_Ready(&RangeType); PyType_Ready(&WindowType); diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -127,4 +127,6 @@ int modify_fname __ARGS((char_u *src, in char_u *do_string_sub __ARGS((char_u *str, char_u *pat, char_u *sub, char_u *flags)); int switch_win __ARGS((win_T **, tabpage_T **, win_T *, tabpage_T *)); void restore_win __ARGS((win_T *, tabpage_T *)); +void list_add_watch __ARGS((list_T *l, listwatch_T *lw)); +void list_rem_watch __ARGS((list_T *l, listwatch_T *lwrem)); /* vim: set ft=c : */ diff --git a/src/testdir/test86.in b/src/testdir/test86.in --- a/src/testdir/test86.in +++ b/src/testdir/test86.in @@ -477,6 +477,9 @@ EOF : call RecVars(oname) :endfor :only +:for buf in g:bufs[1:] +: execute 'bwipeout!' buf +:endfor :" :" Test buffer object :vnew @@ -519,6 +522,62 @@ for expr in ('b[1]','b[:] = ["A", "B"]', # Should not happen in any case cb.append('No exception for ' + expr) EOF +:" +:" Test vim.buffers object +:set hidden +:edit a +:buffer # +:edit b +:buffer # +:edit c +:buffer # +py << EOF +# Check GCing iterator that was not fully exhausted +i = iter(vim.buffers) +cb.append('i:' + str(next(i))) +# and also check creating more then one iterator at a time +i2 = iter(vim.buffers) +cb.append('i2:' + str(next(i2))) +cb.append('i:' + str(next(i))) +# The following should trigger GC and not cause any problems +del i +del i2 +i3 = iter(vim.buffers) +cb.append('i3:' + str(next(i3))) +del i3 + +prevnum = 0 +for b in vim.buffers: + # Check buffer order + if prevnum >= b.number: + cb.append('!!! Buffer numbers not in strictly ascending order') + # Check indexing: vim.buffers[number].number == number + cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + '=' + repr(b)) + prevnum = b.number + +cb.append(str(len(vim.buffers))) + +bnums = list(map(lambda b: b.number, vim.buffers))[1:] + +# Test wiping out buffer with existing iterator +i4 = iter(vim.buffers) +cb.append('i4:' + str(next(i4))) +vim.command('bwipeout! ' + str(bnums.pop(0))) +try: + next(i4) +except vim.error: + pass +else: + cb.append('!!!! No vim.error') +i4 = iter(vim.buffers) +vim.command('bwipeout! ' + str(bnums.pop(-1))) +vim.command('bwipeout! ' + str(bnums.pop(-1))) +cb.append('i4:' + str(next(i4))) +try: + next(i4) +except StopIteration: + cb.append('StopIteration') +EOF :endfun :" :call Test() diff --git a/src/testdir/test86.ok b/src/testdir/test86.ok --- a/src/testdir/test86.ok +++ b/src/testdir/test86.ok @@ -319,3 +319,15 @@ bar Second line Third line foo +i: +i2: +i: +i3: +1:= +6:= +7:= +8:= +4 +i4: +i4: +StopIteration diff --git a/src/testdir/test87.in b/src/testdir/test87.in --- a/src/testdir/test87.in +++ b/src/testdir/test87.in @@ -446,6 +446,9 @@ EOF : call RecVars(oname) :endfor :only +:for buf in g:bufs[1:] +: execute 'bwipeout!' buf +:endfor :" :" Test buffer object :vnew @@ -488,6 +491,62 @@ for expr in ('b[1]','b[:] = ["A", "B"]', # Should not happen in any case cb.append('No exception for ' + expr) EOF +:" +:" Test vim.buffers object +:set hidden +:edit a +:buffer # +:edit b +:buffer # +:edit c +:buffer # +py3 << EOF +# Check GCing iterator that was not fully exhausted +i = iter(vim.buffers) +cb.append('i:' + str(next(i))) +# and also check creating more then one iterator at a time +i2 = iter(vim.buffers) +cb.append('i2:' + str(next(i2))) +cb.append('i:' + str(next(i))) +# The following should trigger GC and not cause any problems +del i +del i2 +i3 = iter(vim.buffers) +cb.append('i3:' + str(next(i3))) +del i3 + +prevnum = 0 +for b in vim.buffers: + # Check buffer order + if prevnum >= b.number: + cb.append('!!! Buffer numbers not in strictly ascending order') + # Check indexing: vim.buffers[number].number == number + cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + '=' + repr(b)) + prevnum = b.number + +cb.append(str(len(vim.buffers))) + +bnums = list(map(lambda b: b.number, vim.buffers))[1:] + +# Test wiping out buffer with existing iterator +i4 = iter(vim.buffers) +cb.append('i4:' + str(next(i4))) +vim.command('bwipeout! ' + str(bnums.pop(0))) +try: + next(i4) +except vim.error: + pass +else: + cb.append('!!!! No vim.error') +i4 = iter(vim.buffers) +vim.command('bwipeout! ' + str(bnums.pop(-1))) +vim.command('bwipeout! ' + str(bnums.pop(-1))) +cb.append('i4:' + str(next(i4))) +try: + next(i4) +except StopIteration: + cb.append('StopIteration') +EOF :endfun :" :call Test() @@ -496,6 +555,7 @@ EOF :call garbagecollect(1) :" :/^start:/,$wq! test.out +:call getchar() ENDTEST start: diff --git a/src/testdir/test87.ok b/src/testdir/test87.ok --- a/src/testdir/test87.ok +++ b/src/testdir/test87.ok @@ -308,3 +308,15 @@ bar Second line Third line foo +i: +i2: +i: +i3: +1:= +6:= +7:= +8:= +4 +i4: +i4: +StopIteration diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 947, +/**/ 946, /**/ 945,