HUGE performance improvement on refresh. fixed #1855

parent 24d6894b
......@@ -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):
......
......@@ -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)
......@@ -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)
......@@ -713,6 +721,7 @@ class RosterInfoTab(Tab):
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)
......
......@@ -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
......@@ -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,20 +400,33 @@ 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
self.built_lines.append(None)
return 0
txt = message.txt
if not txt:
continue
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:
......@@ -421,6 +437,7 @@ class TextWin(Win):
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')
......@@ -446,7 +463,8 @@ class TextWin(Win):
txt[:limit], message.color,
offset,
message.colorized)
lines.append(l)
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:
......@@ -454,7 +472,9 @@ class TextWin(Win):
if txt.startswith('\n'):
txt = txt[1:]
first = False
return lines
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment