summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlouiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 <louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13>2010-12-15 15:40:43 +0000
committerlouiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 <louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13>2010-12-15 15:40:43 +0000
commit0db8bf7d461e1d618ae7efb2f00dc2d99ddf7ca3 (patch)
tree38cf0930bb62a1b26dde8e24644694e94a73f234
parent24d6894b64455ca1135a8b43ddf4ddbf94365a96 (diff)
downloadpoezio-0db8bf7d461e1d618ae7efb2f00dc2d99ddf7ca3.tar.gz
poezio-0db8bf7d461e1d618ae7efb2f00dc2d99ddf7ca3.tar.bz2
poezio-0db8bf7d461e1d618ae7efb2f00dc2d99ddf7ca3.tar.xz
poezio-0db8bf7d461e1d618ae7efb2f00dc2d99ddf7ca3.zip
HUGE performance improvement on refresh. fixed #1855
-rw-r--r--src/core.py8
-rw-r--r--src/room.py17
-rw-r--r--src/tabs.py42
-rw-r--r--src/text_buffer.py38
-rw-r--r--src/windows.py165
5 files changed, 161 insertions, 109 deletions
diff --git a/src/core.py b/src/core.py
index ac0d709e..3fcfabfb 100644
--- a/src/core.py
+++ b/src/core.py
@@ -40,6 +40,7 @@ log = logging.getLogger(__name__)
import multiuserchat as muc
import tabs
+import windows
from connection import connection
from config import config
@@ -83,6 +84,8 @@ class Core(object):
self.stdscr = curses.initscr()
self.init_curses(self.stdscr)
self.xmpp = xmpp
+ self.information_buffer = TextBuffer()
+ self.information_win_size = 0 # Todo, get this from config
default_tab = tabs.InfoTab(self, "Info") if self.xmpp.anon\
else tabs.RosterInfoTab(self)
default_tab.on_gain_focus()
@@ -90,8 +93,7 @@ class Core(object):
# a unique buffer used to store global informations
# that are displayed in almost all tabs, in an
# information window.
- self.information_buffer = TextBuffer()
- self.information_win_size = 2 # Todo, get this from config
+
self.resize_timer = None
self.previous_tab_nb = 0
self.own_nick = config.get('own_nick', self.xmpp.boundjid.bare)
@@ -548,7 +550,6 @@ class Core(object):
Resize the whole screen
"""
with resize_lock:
- # self.resize_timer = None
for tab in self.tabs:
tab.resize()
self.refresh_window()
@@ -1225,6 +1226,7 @@ class Core(object):
Displays an informational message in the "Info" room window
"""
self.information_buffer.add_message(msg, nickname=typ)
+ # TODO: refresh only the correct window in the current tab
self.refresh_window()
def command_quit(self, arg):
diff --git a/src/room.py b/src/room.py
index 62ece926..2fe7a188 100644
--- a/src/room.py
+++ b/src/room.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
-from text_buffer import TextBuffer
+from text_buffer import TextBuffer, MESSAGE_NB_LIMIT
from datetime import datetime
from random import randrange
from config import config
@@ -25,8 +25,6 @@ import common
import theme
class Room(TextBuffer):
- """
- """
def __init__(self, name, nick):
TextBuffer.__init__(self)
self.name = name
@@ -118,6 +116,13 @@ class Room(TextBuffer):
if time: # History messages are colored to be distinguished
color = theme.COLOR_INFORMATION_TEXT
time = time if time is not None else datetime.now()
- if self.pos: # avoid scrolling of one line when one line is received
- self.pos += 1
- self.messages.append(Message(txt, time, nickname, user, color, colorized))
+ message = Message(txt, time, nickname, user, color, colorized)
+ while len(self.messages) > MESSAGE_NB_LIMIT:
+ self.messages.pop(0)
+ self.messages.append(message)
+ for window in self.windows: # make the associated windows
+ # build the lines from the new message
+ nb = window.build_new_message(message)
+ if window.pos != 0:
+ window.scroll_up(nb)
+
diff --git a/src/tabs.py b/src/tabs.py
index f227e2fc..9f78a708 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -199,6 +199,7 @@ class InfoTab(Tab):
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
self.text_win.resize(self.height-2, self.width, 0, 0, self.core.stdscr)
+ self.text_win.rebuild_everything(self._room)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
def refresh(self, tabs, informations, _):
@@ -308,10 +309,12 @@ class MucTab(ChatTab):
ChatTab.__init__(self, core, room)
self.topic_win = windows.Topic()
self.text_win = windows.TextWin()
+ room.add_window(self.text_win)
self.v_separator = windows.VerticalSeparator()
self.user_win = windows.UserList()
self.info_header = windows.MucInfoWin()
self.info_win = windows.TextWin()
+ self.core.information_buffer.add_window(self.info_win)
self.tab_win = windows.GlobalInfoBar()
self.input = windows.MessageInput()
self.ignores = [] # set of Users
@@ -485,10 +488,11 @@ class MucTab(ChatTab):
text_width = (self.width//10)*9
self.topic_win.resize(1, self.width, 0, 0, self.core.stdscr)
self.text_win.resize(self.height-4-self.core.information_win_size, text_width, 1, 0, self.core.stdscr)
+ self.text_win.rebuild_everything(self._room)
self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10), self.core.stdscr)
self.user_win.resize(self.height-3, self.width-text_width-1, 1, text_width+1, self.core.stdscr)
self.info_header.resize(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
- self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
+ self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, self.core.stdscr, self.core.information_buffer)
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
@@ -499,9 +503,9 @@ class MucTab(ChatTab):
self.text_win.refresh(self._room)
self.v_separator.refresh()
self.user_win.refresh(self._room.users)
- self.info_header.refresh(self._room)
- self.info_win.refresh(informations)
+ self.info_header.refresh(self._room, self.text_win)
self.tab_win.refresh(tabs, tabs[0])
+ self.info_win.refresh(informations)
self.input.refresh()
def on_input(self, key):
@@ -549,10 +553,10 @@ class MucTab(ChatTab):
curses.curs_set(1)
def on_scroll_up(self):
- self._room.scroll_up(self.text_win.height-1)
+ self.text_win.scroll_up(self.text_win.height-1)
def on_scroll_down(self):
- self._room.scroll_down(self.text_win.height-1)
+ self.text_win.scroll_down(self.text_win.height-1)
def on_info_win_size_changed(self):
text_width = (self.width//10)*9
@@ -573,8 +577,10 @@ class PrivateTab(ChatTab):
def __init__(self, core, room):
ChatTab.__init__(self, core, room)
self.text_win = windows.TextWin()
+ room.add_window(self.text_win)
self.info_header = windows.PrivateInfoWin()
self.info_win = windows.TextWin()
+ self.core.information_buffer.add_window(self.info_win)
self.tab_win = windows.GlobalInfoBar()
self.input = windows.MessageInput()
# keys
@@ -601,8 +607,9 @@ class PrivateTab(ChatTab):
def resize(self):
Tab.resize(self)
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
+ self.text_win.rebuild_everything(self._room)
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
- self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
+ self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr, self.core.information_buffer)
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
@@ -610,7 +617,7 @@ class PrivateTab(ChatTab):
if not self.visible:
return
self.text_win.refresh(self._room)
- self.info_header.refresh(self._room)
+ self.info_header.refresh(self._room, self.text_win)
self.info_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
self.input.refresh()
@@ -644,15 +651,15 @@ class PrivateTab(ChatTab):
curses.curs_set(1)
def on_scroll_up(self):
- self._room.scroll_up(self.text_win.height-1)
+ self.text_win.scroll_up(self.text_win.height-1)
def on_scroll_down(self):
- self._room.scroll_down(self.text_win.height-1)
+ self.text_win.scroll_down(self.text_win.height-1)
def on_info_win_size_changed(self):
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
- self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
+ self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr, None)
def get_room(self):
return self._room
@@ -673,6 +680,7 @@ class RosterInfoTab(Tab):
self.v_separator = windows.VerticalSeparator()
self.tab_win = windows.GlobalInfoBar()
self.info_win = windows.TextWin()
+ self.core.information_buffer.add_window(self.info_win)
self.roster_win = windows.RosterWin()
self.contact_info_win = windows.ContactInfoWin()
self.default_help_message = windows.HelpText("Enter commands with “/”. “o”: toggle offline show")
@@ -696,7 +704,7 @@ class RosterInfoTab(Tab):
info_width = self.width-roster_width-1
self.v_separator.resize(self.height-2, 1, 0, roster_width, self.core.stdscr)
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
- self.info_win.resize(self.height-2, info_width, 0, roster_width+1, self.core.stdscr)
+ self.info_win.resize(self.height-2, info_width, 0, roster_width+1, self.core.stdscr, self.core.information_buffer)
self.roster_win.resize(self.height-2-3, roster_width, 0, 0, self.core.stdscr)
self.contact_info_win.resize(3, roster_width, self.height-2-3, 0, self.core.stdscr)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
@@ -706,13 +714,14 @@ class RosterInfoTab(Tab):
if isinstance(self.input, windows.CommandInput) and\
not self.input.help_message:
self.complete_commands(self.input)
-
+
def refresh(self, tabs, informations, roster):
if not self.visible:
return
self.v_separator.refresh()
self.roster_win.refresh(roster)
self.contact_info_win.refresh(self.roster_win.get_selected_row())
+ # self.core.global_information_win.refresh(informations)
self.info_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
self.input.refresh()
@@ -840,9 +849,11 @@ class ConversationTab(ChatTab):
self.color_state = theme.COLOR_TAB_NORMAL
self._name = jid # a conversation tab is linked to one specific full jid OR bare jid
self.text_win = windows.TextWin()
+ text_buffer.add_window(self.text_win)
self.upper_bar = windows.ConversationStatusMessageWin()
self.info_header = windows.ConversationInfoWin()
self.info_win = windows.TextWin()
+ self.core.information_buffer.add_window(self.info_win)
self.tab_win = windows.GlobalInfoBar()
self.input = windows.MessageInput()
# keys
@@ -869,9 +880,10 @@ class ConversationTab(ChatTab):
def resize(self):
Tab.resize(self)
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 1, 0, self.core.stdscr)
+ self.text_win.rebuild_everything(self._room)
self.upper_bar.resize(1, self.width, 0, 0, self.core.stdscr)
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
- self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
+ self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr, self.core.information_buffer)
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
@@ -914,10 +926,10 @@ class ConversationTab(ChatTab):
curses.curs_set(1)
def on_scroll_up(self):
- self._room.scroll_up(self.text_win.height-1)
+ self.text_win.scroll_up(self.text_win.height-1)
def on_scroll_down(self):
- self._room.scroll_down(self.text_win.height-1)
+ self.text_win.scroll_down(self.text_win.height-1)
def on_info_win_size_changed(self):
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
diff --git a/src/text_buffer.py b/src/text_buffer.py
index 43bf008f..32c6f725 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -18,10 +18,15 @@
Define the TextBuffer class
"""
+import logging
+log = logging.getLogger(__name__)
+
from message import Message
from datetime import datetime
import theme
+MESSAGE_NB_LIMIT = 16384
+
class TextBuffer(object):
"""
This class just keep trace of messages, in a list with various
@@ -29,15 +34,28 @@ class TextBuffer(object):
"""
def __init__(self):
self.messages = [] # Message objects
- self.pos = 0
+ self.windows = [] # we keep track of one or more windows
+ # so we can pass the new messages to them, as they are added, so
+ # they (the windows) can built the lines from the new message
+
+ def add_window(self, win):
+ self.windows.append(win)
def add_message(self, txt, time=None, nickname=None, colorized=False):
color = theme.COLOR_NORMAL_TEXT
user = None
time = time or datetime.now()
- if self.pos: # avoid scrolling of one line when one line is received
- self.pos += 1
- self.messages.append(Message(txt, time, nickname, user, color, colorized))
+ # if self.pos: # avoid scrolling of one line when one line is received
+ # self.pos += 1
+ msg = Message(txt, time, nickname, user, color, colorized)
+ self.messages.append(msg)
+ while len(self.messages) > MESSAGE_NB_LIMIT:
+ self.messages.pop(0)
+ for window in self.windows: # make the associated windows
+ # build the lines from the new message
+ nb = window.build_new_message(msg)
+ if window.pos != 0:
+ window.scroll_up(nb)
def remove_line_separator(self):
"""
@@ -52,15 +70,3 @@ class TextBuffer(object):
"""
if None not in self.messages:
self.messages.append(None)
-
- def scroll_up(self, dist=14):
- # The pos can grow a lot over the top of the number of
- # available lines, it will be fixed on the next refresh of the
- # screen anyway
- self.pos += dist
-
- def scroll_down(self, dist=14):
- self.pos -= dist
- if self.pos <= 0:
- self.pos = 0
-
diff --git a/src/windows.py b/src/windows.py
index 81f81f60..13383536 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -49,6 +49,8 @@ import theme
g_lock = Lock()
+LINES_NB_LIMIT = 16384
+
class Win(object):
def __init__(self):
pass
@@ -186,14 +188,14 @@ class InfoWin(Win):
def __init__(self):
Win.__init__(self)
- def print_scroll_position(self, text_buffer):
+ def print_scroll_position(self, window):
"""
Print, link in Weechat, a -PLUS(n)- where n
is the number of available lines to scroll
down
"""
- if text_buffer.pos > 0:
- plus = ' -PLUS(%s)-' % text_buffer.pos
+ if window.pos > 0:
+ plus = ' -PLUS(%s)-' % window.pos
self.addstr(plus, curses.color_pair(theme.COLOR_SCROLLABLE_NUMBER) | curses.A_BOLD)
class PrivateInfoWin(InfoWin):
@@ -341,14 +343,15 @@ class MucInfoWin(InfoWin):
def resize(self, height, width, y, x, stdscr):
self._resize(height, width, y, x, stdscr)
- def refresh(self, room):
+ def refresh(self, room, window=None):
with g_lock:
self._win.erase()
self.write_room_name(room)
self.write_own_nick(room)
self.write_disconnected(room)
self.write_role(room)
- self.print_scroll_position(room)
+ if window:
+ self.print_scroll_position(window)
self.finish_line(theme.COLOR_INFORMATION_BAR)
self._refresh()
@@ -397,64 +400,81 @@ class MucInfoWin(InfoWin):
class TextWin(Win):
def __init__(self):
Win.__init__(self)
+ self.pos = 0
+ self.built_lines = [] # Each new message is built and kept here.
+ # on resize, we rebuild all the messages
+
+ def scroll_up(self, dist=14):
+ # The pos can grow a lot over the top of the number of
+ # available lines, it will be fixed on the next refresh of the
+ # screen anyway
+ self.pos += dist
+
+ def scroll_down(self, dist=14):
+ self.pos -= dist
+ if self.pos <= 0:
+ self.pos = 0
- def build_lines_from_messages(self, messages):
+ def build_new_message(self, message):
"""
- From all the existing messages in the window, create the that will
- be displayed on the screen
+ Take one message, build it and add it to the list
+ Return the number of lines that are built for the given
+ message.
"""
- lines = []
- for message in messages:
- if message == None: # line separator
- lines.append(None)
- continue
- txt = message.txt
- if not txt:
- continue
+ if message == None: # line separator
+ self.built_lines.append(None)
+ return 0
+ txt = message.txt
+ if not txt:
+ return 0
# length of the time
- offset = 9+len(theme.CHAR_TIME_LEFT[:1])+len(theme.CHAR_TIME_RIGHT[:1])
- if message.nickname and len(message.nickname) >= 30:
- nick = message.nickname[:30]+'…'
+ offset = 9+len(theme.CHAR_TIME_LEFT[:1])+len(theme.CHAR_TIME_RIGHT[:1])
+ if message.nickname and len(message.nickname) >= 30:
+ nick = message.nickname[:30]+'…'
+ else:
+ nick = message.nickname
+ if nick:
+ offset += len(nick) + 2 # + nick + spaces length
+ first = True
+ this_line_was_broken_by_space = False
+ nb = 0
+ while txt != '':
+ if txt[:self.width-offset].find('\n') != -1:
+ limit = txt[:self.width-offset].find('\n')
else:
- nick = message.nickname
- if nick:
- offset += len(nick) + 2 # + nick + spaces length
- first = True
- this_line_was_broken_by_space = False
- while txt != '':
- if txt[:self.width-offset].find('\n') != -1:
- limit = txt[:self.width-offset].find('\n')
- else:
- # break between words if possible
- if len(txt) >= self.width-offset:
- limit = txt[:self.width-offset].rfind(' ')
- this_line_was_broken_by_space = True
- if limit <= 0:
- limit = self.width-offset
- this_line_was_broken_by_space = False
- else:
- limit = self.width-offset-1
+ # break between words if possible
+ if len(txt) >= self.width-offset:
+ limit = txt[:self.width-offset].rfind(' ')
+ this_line_was_broken_by_space = True
+ if limit <= 0:
+ limit = self.width-offset
this_line_was_broken_by_space = False
- color = message.user.color if message.user else None
- if not first:
- nick = None
- time = None
- else:
- time = message.time
- l = Line(nick, color,
- time,
- txt[:limit], message.color,
- offset,
- message.colorized)
- lines.append(l)
- if this_line_was_broken_by_space:
- txt = txt[limit+1:] # jump the space at the start of the line
else:
- txt = txt[limit:]
- if txt.startswith('\n'):
- txt = txt[1:]
- first = False
- return lines
+ limit = self.width-offset-1
+ this_line_was_broken_by_space = False
+ color = message.user.color if message.user else None
+ if not first:
+ nick = None
+ time = None
+ else:
+ time = message.time
+ l = Line(nick, color,
+ time,
+ txt[:limit], message.color,
+ offset,
+ message.colorized)
+ self.built_lines.append(l)
+ nb += 1
+ if this_line_was_broken_by_space:
+ txt = txt[limit+1:] # jump the space at the start of the line
+ else:
+ txt = txt[limit:]
+ if txt.startswith('\n'):
+ txt = txt[1:]
+ first = False
+ while len(self.built_lines) > LINES_NB_LIMIT:
+ self.built_lines.pop(0)
+ return nb
def refresh(self, room):
"""
@@ -465,13 +485,14 @@ class TextWin(Win):
return
with g_lock:
self._win.erase()
- lines = self.build_lines_from_messages(room.messages)
- if room.pos + self.height > len(lines):
- room.pos = len(lines) - self.height
- if room.pos < 0:
- room.pos = 0
- if room.pos != 0:
- lines = lines[-self.height-room.pos:-room.pos]
+ # lines = self.build_lines_from_messages(room.messages)
+ lines = self.built_lines
+ if self.pos + self.height > len(lines):
+ self.pos = len(lines) - self.height
+ if self.pos < 0:
+ self.pos = 0
+ if self.pos != 0:
+ lines = lines[-self.height-self.pos:-self.pos]
else:
lines = lines[-self.height:]
y = 0
@@ -562,8 +583,15 @@ class TextWin(Win):
self.addnstr(theme.CHAR_TIME_RIGHT, curses.color_pair(theme.COLOR_TIME_LIMITER))
self.addstr(' ')
- def resize(self, height, width, y, x, stdscr):
+ def resize(self, height, width, y, x, stdscr, room=None):
self._resize(height, width, y, x, stdscr)
+ if room:
+ self.rebuild_everything(room)
+
+ def rebuild_everything(self, room):
+ self.built_lines = []
+ for message in room.messages:
+ self.build_new_message(message)
class HelpText(Win):
"""
@@ -824,7 +852,6 @@ class Input(Win):
begin = self.text.split()[-1].lower()
else:
begin = ''
- log.debug('BEGIN: [%s]\n' % begin)
hit_list = [] # list of matching nicks
for word in word_list:
if word.lower().startswith(begin):
@@ -895,15 +922,15 @@ class Input(Win):
def do_command(self, key, reset=True):
if key in self.key_func:
return self.key_func[key]()
- if not key or len(key) > 1:
- return False # ignore non-handled keyboard shortcuts
+ # if not key or len(key) > 1:
+ # return False # ignore non-handled keyboard shortcuts
self.reset_completion()
self.text = self.text[:self.pos+self.line_pos]+key+self.text[self.pos+self.line_pos:]
(y, x) = self._win.getyx()
if x == self.width-1:
- self.line_pos += 1
+ self.line_pos += len(key)
else:
- self.pos += 1
+ self.pos += len(key)
if reset:
self.rewrite_text()
return True