diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | setup.py | 16 | ||||
-rw-r--r-- | src/pooptmodule.c | 279 | ||||
-rw-r--r-- | src/room.py | 4 | ||||
-rw-r--r-- | src/text_buffer.py | 7 | ||||
-rw-r--r-- | src/windows.py | 76 |
6 files changed, 332 insertions, 52 deletions
@@ -6,6 +6,8 @@ LOCALEDIR=$(DATADIR)/locale MANDIR=$(DATADIR)/man all: Makefile + python3 setup.py build + cp build/lib.linux-x86_64-3.2/poopt.cpython-32mu.so src/ clean: find ./ -name \*.pyc -delete diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..50d787da --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup, Extension + +module_poopt = Extension('poopt', + sources = ['src/pooptmodule.c']) + +setup (name = 'BuildLines', + version = '0.0.1', + description = 'Poezio Optimizations', + ext_modules = [module_poopt], + author = 'Florent Le Coz', + author_email = 'louiz@louiz.org', + long_description = """ + a python3 module for poezio, used to replace some time-critical + python functions that are too slow. If compiled, poezio will use this module, + otherwise it will just use the equivalent python functions. + """) diff --git a/src/pooptmodule.c b/src/pooptmodule.c new file mode 100644 index 00000000..289313f3 --- /dev/null +++ b/src/pooptmodule.c @@ -0,0 +1,279 @@ +/* Copyright 2010-2011 Florent Le Coz <louiz@louiz.org> */ + +/* This file is part of Poezio. */ + +/* Poezio is free software: you can redistribute it and/or modify */ +/* it under the terms of the MIT license. See the COPYING file. */ + +/** The poopt python3 module +**/ + +/* This file is a python3 module for poezio, used to replace some time-critical +python functions that are too slow. If compiled, poezio will use this module, +otherwise it will just use the equivalent python functions. */ + +#define PY_SSIZE_T_CLEAN + +#include "Python.h" + +PyObject *ErrorObject; + +/*** + The module functions + ***/ + +/* cut_text: takes a string and returns a tuple of int. + Each two int tuple is a line, represented by the ending position it (where it should be cut). + Not that this position is calculed using the position of the python string characters, + not just the individual bytes. + For example, poopt_cut_text("vivent les frigidaires", 6); + will return [(0, 6), (7, 10), (11, 17), (17, 22)], meaning that the lines are + "vivent", "les", "frigid" and "aires" +*/ +PyDoc_STRVAR(poopt_cut_text_doc, "cut_text(width, text)\n\n\nReturn the list of strings, cut according to the given size."); + +static PyObject *poopt_cut_text(PyObject *self, PyObject *args) +{ + /* int length; */ + unsigned char *buffer; + int width; + + if (PyArg_ParseTuple(args, "si", &buffer, &width) == 0) + return NULL; + + int bpos = 0; /* the real position in the char* */ + int spos = 0; /* the position, considering UTF-8 chars */ + int last_space = -1; + int start_pos = 0; + + PyObject* retlist = PyList_New(0); + + while (buffer[bpos]) + { + if (buffer[bpos] == ' ') + last_space = spos; + else if (buffer[bpos] == '\n') + { + if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, spos)) == -1) + return NULL; + start_pos = spos + 1; + last_space = -1; + } + else if ((spos - start_pos) >= width) + { + if (last_space == -1) + { + if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, spos)) == -1) + return NULL; + start_pos = spos; + } + else + { + if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, last_space)) == -1) + return NULL; + start_pos = last_space + 1; + last_space = -1; + } + } + if (buffer[bpos] == 25) /* \x19 */ + { + spos++; + bpos += 2; + } + else + if (buffer[bpos] <= 127) /* ASCII char on one byte */ + bpos += 1; + else if (buffer[bpos] >= 194 && buffer[bpos] <= 223) + bpos += 2; + else if (buffer[bpos] >= 224 && buffer[bpos] <= 239) + bpos += 3; + else if (buffer[bpos] >= 240 && buffer[bpos] <= 244) + bpos += 4; + else + return NULL; + spos++; + } + if (PyList_Append(retlist, Py_BuildValue("(i,i)", start_pos, spos)) == -1) + return NULL; + return retlist; +} + +/*** + Module initialization. Just taken from the xxmodule.c template from the python sources. + ***/ +static PyTypeObject Str_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "pooptmodule.Str", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_reserved*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ---------- */ + +static PyObject * +null_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyTypeObject Null_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "pooptmodule.Null", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_reserved*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + null_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /* see PyInit_xx */ /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + +/* List of functions defined in the module */ + +static PyMethodDef poopt_methods[] = { + {"cut_text", poopt_cut_text, METH_VARARGS, + poopt_cut_text_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(module_doc, + "This is a template module just for instruction. And poopt."); + +/* Initialization function for the module (*must* be called PyInit_xx) */ + +static struct PyModuleDef pooptmodule = { + PyModuleDef_HEAD_INIT, + "poopt", + module_doc, + -1, + poopt_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit_poopt(void) +{ + PyObject *m = NULL; + + /* Due to cross platform compiler issues the slots must be filled + * here. It's required for portability to Windows without requiring + * C++. */ + Null_Type.tp_base = &PyBaseObject_Type; + Null_Type.tp_new = PyType_GenericNew; + Str_Type.tp_base = &PyUnicode_Type; + + /* Finalize the type object including setting type of the new type + * object; doing it here is required for portability, too. */ + /* if (PyType_Ready(&Xxo_Type) < 0) */ + /* goto fail; */ + + /* Create the module and add the functions */ + m = PyModule_Create(&pooptmodule); + if (m == NULL) + goto fail; + + /* Add some symbolic constants to the module */ + if (ErrorObject == NULL) { + ErrorObject = PyErr_NewException("poopt.error", NULL, NULL); + if (ErrorObject == NULL) + goto fail; + } + Py_INCREF(ErrorObject); + PyModule_AddObject(m, "error", ErrorObject); + + /* Add Str */ + if (PyType_Ready(&Str_Type) < 0) + goto fail; + PyModule_AddObject(m, "Str", (PyObject *)&Str_Type); + + /* Add Null */ + if (PyType_Ready(&Null_Type) < 0) + goto fail; + PyModule_AddObject(m, "Null", (PyObject *)&Null_Type); + return m; + fail: + Py_XDECREF(m); + return NULL; +} + +/* /\* test function *\/ */ +/* int main(void) */ +/* { */ +/* char coucou[] = "vive le foutre, le beurre et le caca boudin"; */ + +/* cut_text(coucou, 8); */ +/* } */ diff --git a/src/room.py b/src/room.py index 7719dd67..1ca3f599 100644 --- a/src/room.py +++ b/src/room.py @@ -120,7 +120,9 @@ class Room(TextBuffer): nick_color = highlight time = time or datetime.now() message = Message(txt='%s\x19o'%(txt,), nick_color=nick_color, - time=time, nickname=nickname, user=user) + time=time, str_time=time.strftime("%Y-%m-%d %H:%M:%S")\ + if history else time.strftime("%H:%M:%S"),\ + nickname=nickname, user=user) while len(self.messages) > self.messages_nb_limit: self.messages.pop(0) self.messages.append(message) diff --git a/src/text_buffer.py b/src/text_buffer.py index a3b5b1fb..a6465da3 100644 --- a/src/text_buffer.py +++ b/src/text_buffer.py @@ -27,7 +27,7 @@ from datetime import datetime import theme from config import config -Message = collections.namedtuple('Message', 'txt nick_color time nickname user') +Message = collections.namedtuple('Message', 'txt nick_color time str_time nickname user') class TextBuffer(object): """ @@ -45,8 +45,11 @@ class TextBuffer(object): self.windows.append(win) def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None): + time = time or datetime.now() msg = Message(txt='%s\x19o'%(txt,), nick_color=nick_color, - time=time or datetime.now(), nickname=nickname, user=None) + time=time, str_time=time.strftime("%Y-%m-%d %H:%M:%S")\ + if history else time.strftime("%H:%M:%S"),\ + nickname=nickname, user=None) self.messages.append(msg) while len(self.messages) > self.messages_nb_limit: self.messages.pop(0) diff --git a/src/windows.py b/src/windows.py index 8e21d0f8..59365dc8 100644 --- a/src/windows.py +++ b/src/windows.py @@ -28,6 +28,7 @@ from threading import Lock from contact import Contact, Resource from roster import RosterGroup, roster +from poopt import cut_text # from message import Line from tabs import MIN_WIDTH, MIN_HEIGHT @@ -41,7 +42,10 @@ import wcwidth import singleton import collections -Line = collections.namedtuple('Line', 'text text_offset nickname_color time nickname') +# msg is a reference to the corresponding Message tuple. text_start and text_end are the position +# delimiting the text in this line. +# first is a bool telling if this is the first line of the message. +Line = collections.namedtuple('Line', 'msg start_pos end_pos first') g_lock = Lock() @@ -535,23 +539,7 @@ class TextWin(Win): Return the number of lines that are built for the given message. """ - def cut_text(text, width): - """ - returns the text that should be displayed on the line, and the rest - of the text, in a tuple - """ - cutted = wcwidth.widthcut(text, width) or text[:width] - limit = cutted.find('\n') - if limit >= 0: - return (text[limit+1:], text[:limit]) - if not wcwidth.wcsislonger(text, width): - return ('', text) - limit = cutted.rfind(' ') - if limit <= 0: - return (text[len(cutted):], cutted) - else: - return (text[limit+1:], text[:limit]) - + log.debug('LEEEN: %s [%s]' % (len(message.txt), repr(message.txt))) if message is None: # line separator self.built_lines.append(None) return 0 @@ -565,7 +553,7 @@ class TextWin(Win): offset = 20 else: offset = 9 - if theme.CHAR_TIME_RIGHT: + if theme.CHAR_TIME_LEFT: offset += 1 if theme.CHAR_TIME_RIGHT: offset += 1 @@ -577,36 +565,18 @@ class TextWin(Win): if nick: offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length first = True - nb = 0 - while txt != '': - (txt, cutted_txt) = cut_text(txt, self.width-offset-1) - if first: - if message.nick_color: - color = message.nick_color - elif message.user: - color = message.user.color - else: - color = None - else: - color = None - if first: - if history: - time = message.time.strftime("%Y-%m-%d %H:%M:%S") - else: - time = message.time.strftime("%H:%M:%S") - nickname = nick - else: - time = None - nickname = None - self.built_lines.append(Line(text=cutted_txt, - text_offset=offset, - nickname_color=color, time=time, - nickname=nickname)) - nb += 1 + text_len = len(txt) + offset = (3 if message.nickname else 1) + len(message.str_time)+len(message.nickname or '') + lines = cut_text(txt, self.width-offset-1) + for line in lines: + self.built_lines.append(Line(msg=message, + start_pos=line[0], + end_pos=line[1], + first=first)) first = False while len(self.built_lines) > self.lines_nb_limit: self.built_lines.pop(0) - return nb + return len(lines) def refresh(self, room): log.debug('Refresh: %s'%self.__class__.__name__) @@ -623,15 +593,23 @@ class TextWin(Win): if line is None: self.write_line_separator() else: - self.write_time(line.time) - self.write_nickname(line.nickname, line.nickname_color) + msg = line.msg + if line.first: + if msg.nick_color: + color = msg.nick_color + elif msg.user: + color = msg.user.color + else: + color = None + self.write_time(msg.str_time) + self.write_nickname(msg.nickname, color) if y != self.height-1: self.addstr('\n') self._win.attrset(0) for y, line in enumerate(lines): if not line: continue - self.write_text(y, line.text_offset, line.text) + self.write_text(y, (3 if line.msg.nickname else 1) + len(line.msg.str_time)+len(line.msg.nickname or ''), line.msg.txt[line.start_pos:line.end_pos]) if y != self.height-1: self.addstr('\n') self._win.attrset(0) |