HUGE performance improvement on refresh. fixed #1855

This commit is contained in:
louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 2010-12-15 15:40:43 +00:00
parent 24d6894b64
commit 0db8bf7d46
5 changed files with 161 additions and 109 deletions

View file

@ -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):

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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 build_lines_from_messages(self, 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_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