mirror of
https://github.com/paul-nameless/tg
synced 2024-11-22 03:43:19 +00:00
Merge branch 'master' into msg-replies
This commit is contained in:
commit
1441cd2197
4 changed files with 85 additions and 55 deletions
|
@ -7,6 +7,7 @@ DEFAULT_CONFIG = os.path.expanduser("~/.config/tg/tg.conf")
|
|||
DEFAULT_FILES = os.path.expanduser("~/.cache/tg/")
|
||||
max_download_size = "10MB"
|
||||
record_cmd = None
|
||||
long_msg_cmd = "vim -c 'startinsert' {file_path}"
|
||||
|
||||
|
||||
def get_cfg(config=DEFAULT_CONFIG):
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import curses
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from signal import SIGWINCH, signal
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
@ -60,6 +62,8 @@ class Controller:
|
|||
"updateMessageSendSucceeded": self.update_msg_send_succeeded,
|
||||
"updateNewMessage": self.update_new_msg,
|
||||
}
|
||||
self.chat_size = 0.5
|
||||
signal(SIGWINCH, self.resize_handler)
|
||||
|
||||
def send_file(self, send_file_fun, *args, **kwargs):
|
||||
file_path = self.view.status.get_input()
|
||||
|
@ -118,35 +122,64 @@ class Controller:
|
|||
log.info("Opening file: %s", path)
|
||||
s.open_file(path)
|
||||
|
||||
def write_long_msg(self):
|
||||
file_path = "/tmp/tg-msg.txt"
|
||||
with suspend(self.view) as s:
|
||||
s.call(config.long_msg_cmd.format(file_path=file_path))
|
||||
if not os.path.isfile(file_path):
|
||||
return
|
||||
with open(file_path) as f:
|
||||
msg = f.read().strip()
|
||||
os.remove(file_path)
|
||||
if msg:
|
||||
self.model.send_message(text=msg)
|
||||
with self.lock:
|
||||
self.view.status.draw("Message sent")
|
||||
|
||||
def resize_handler(self, signum, frame):
|
||||
curses.endwin()
|
||||
self.view.stdscr.refresh()
|
||||
self.resize()
|
||||
|
||||
def resize(self):
|
||||
rows, cols = self.view.stdscr.getmaxyx()
|
||||
# If we didn't clear the screen before doing this,
|
||||
# the original window contents would remain on the screen
|
||||
# and we would see the window text twice.
|
||||
self.view.stdscr.erase()
|
||||
self.view.stdscr.noutrefresh()
|
||||
|
||||
self.view.chats.resize(rows, cols, self.chat_size)
|
||||
self.view.msgs.resize(rows, cols, 1 - self.chat_size)
|
||||
self.view.status.resize(rows, cols)
|
||||
self.render()
|
||||
|
||||
def handle_msgs(self) -> str:
|
||||
self.view.chats.resize(0.2)
|
||||
self.view.msgs.resize(0.8)
|
||||
self.refresh_chats()
|
||||
self.chat_size = 0.2
|
||||
self.resize()
|
||||
|
||||
while True:
|
||||
|
||||
repeat_factor, keys = self.view.get_keys(
|
||||
self.view.chats.h, self.view.chats.w
|
||||
)
|
||||
repeat_factor, keys = self.view.get_keys()
|
||||
log.info("Pressed keys: %s", keys)
|
||||
if keys == "q":
|
||||
return "QUIT"
|
||||
elif keys == "]":
|
||||
if self.model.next_chat():
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
elif keys == "[":
|
||||
if self.model.prev_chat():
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
elif keys == "J":
|
||||
if self.model.next_msg(10):
|
||||
self.refresh_msgs()
|
||||
elif keys == "K":
|
||||
if self.model.prev_msg(10):
|
||||
self.refresh_msgs()
|
||||
elif keys in ("j", "^P"):
|
||||
elif keys in ("j", "^N"):
|
||||
if self.model.next_msg(repeat_factor):
|
||||
self.refresh_msgs()
|
||||
elif keys in ("k", "^N"):
|
||||
elif keys in ("k", "^P"):
|
||||
if self.model.prev_msg(repeat_factor):
|
||||
self.refresh_msgs()
|
||||
elif keys == "G":
|
||||
|
@ -198,15 +231,16 @@ class Controller:
|
|||
# reply to this msg
|
||||
# print to status line
|
||||
pass
|
||||
elif keys == "I":
|
||||
# open vim or emacs to write long messages
|
||||
pass
|
||||
elif keys in ("i", "a"):
|
||||
# write new message
|
||||
msg = self.view.status.get_input()
|
||||
if msg:
|
||||
self.model.send_message(text=msg)
|
||||
self.view.status.draw(f"Sent: {msg}")
|
||||
with self.lock:
|
||||
self.view.status.draw(f"Sent: {msg}")
|
||||
|
||||
elif keys in ("I", "A"):
|
||||
self.write_long_msg()
|
||||
|
||||
elif keys in ("h", "^D"):
|
||||
return "BACK"
|
||||
|
@ -216,14 +250,12 @@ class Controller:
|
|||
breakpoint()
|
||||
|
||||
def handle_chats(self) -> None:
|
||||
self.view.chats.resize(0.5)
|
||||
self.view.msgs.resize(0.5)
|
||||
self.refresh_chats()
|
||||
self.chat_size = 0.5
|
||||
self.resize()
|
||||
|
||||
while True:
|
||||
|
||||
repeat_factor, keys = self.view.get_keys(
|
||||
self.view.chats.h, self.view.chats.w
|
||||
)
|
||||
repeat_factor, keys = self.view.get_keys()
|
||||
log.info("Pressed keys: %s", keys)
|
||||
if keys == "q":
|
||||
return
|
||||
|
@ -231,29 +263,28 @@ class Controller:
|
|||
rc = self.handle_msgs()
|
||||
if rc == "QUIT":
|
||||
return
|
||||
self.view.chats.resize(0.5)
|
||||
self.view.msgs.resize(0.5)
|
||||
self.refresh_chats()
|
||||
self.chat_size = 0.5
|
||||
self.resize()
|
||||
|
||||
elif keys in ("j", "^N"):
|
||||
if self.model.next_chat(repeat_factor):
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
elif keys in ("k", "^P"):
|
||||
if self.model.prev_chat(repeat_factor):
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
elif keys in ("J",):
|
||||
if self.model.next_chat(10):
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
elif keys in ("K",):
|
||||
if self.model.prev_chat(10):
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
elif keys == "gg":
|
||||
if self.model.first_chat():
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
elif keys == "bp":
|
||||
with suspend(self.view):
|
||||
|
@ -292,11 +323,11 @@ class Controller:
|
|||
chat_id, notification_settings
|
||||
)
|
||||
|
||||
def refresh_chats(self) -> None:
|
||||
def render(self) -> None:
|
||||
with self.lock:
|
||||
# using lock here, because refresh_chats is used from another
|
||||
# using lock here, because render is used from another
|
||||
# thread by tdlib python wrapper
|
||||
page_size = self.view.msgs.h
|
||||
page_size = self.view.chats.h
|
||||
chats = self.model.get_chats(
|
||||
self.model.current_chat, page_size, MSGS_LEFT_SCROLL_THRESHOLD
|
||||
)
|
||||
|
@ -451,7 +482,7 @@ class Controller:
|
|||
if chat["id"] == current_chat_id:
|
||||
self.model.current_chat = i
|
||||
break
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
@handle_exception
|
||||
def update_chat_notification_settings(self, update):
|
||||
|
@ -461,7 +492,7 @@ class Controller:
|
|||
self.model.chats.update_chat(
|
||||
chat_id, notification_settings=notification_settings
|
||||
)
|
||||
self.refresh_chats()
|
||||
self.render()
|
||||
|
||||
@handle_exception
|
||||
def update_msg_send_succeeded(self, update):
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import logging
|
||||
import logging.handlers
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
from curses import window, wrapper
|
||||
from functools import partial
|
||||
|
@ -171,6 +170,7 @@ def main():
|
|||
cfg.get("max_download_size", config.max_download_size)
|
||||
)
|
||||
config.record_cmd = cfg.get("record_cmd")
|
||||
config.long_msg_cmd = cfg.get("long_msg_cmd", config.long_msg_cmd)
|
||||
tg.login()
|
||||
|
||||
wrapper(partial(run, tg))
|
||||
|
|
|
@ -35,6 +35,7 @@ class View:
|
|||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
curses.curs_set(0)
|
||||
|
||||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
# init white color first to initialize colors correctly
|
||||
|
@ -46,15 +47,15 @@ class View:
|
|||
self.status = status_view
|
||||
self.max_read = 2048
|
||||
|
||||
def get_keys(self, y: int, x: int) -> Tuple[int, str]:
|
||||
def get_keys(self) -> Tuple[int, str]:
|
||||
keys = repeat_factor = ""
|
||||
|
||||
for _ in range(MAX_KEYBINDING_LENGTH):
|
||||
ch = self.stdscr.getch(y, x)
|
||||
ch = self.stdscr.getch()
|
||||
log.info("raw ch without unctrl: %s", ch)
|
||||
try:
|
||||
key = curses.unctrl(ch).decode()
|
||||
except UnicodeDecodeError:
|
||||
except Exception:
|
||||
log.warning("cant uncrtl: %s", ch)
|
||||
break
|
||||
if key.isdigit():
|
||||
|
@ -77,14 +78,15 @@ class StatusView:
|
|||
self.w = curses.COLS
|
||||
self.y = curses.LINES - 1
|
||||
self.x = 0
|
||||
self.stdscr = stdscr
|
||||
self.win = stdscr.subwin(self.h, self.w, self.y, self.x)
|
||||
self._refresh = self.win.refresh
|
||||
|
||||
def resize(self):
|
||||
self.w = curses.COLS
|
||||
self.y = curses.LINES - 1
|
||||
def resize(self, rows: int, cols: int):
|
||||
self.w = cols
|
||||
self.y = rows - 1
|
||||
self.win.resize(self.h, self.w)
|
||||
self.win.wmove(self.y, self.x)
|
||||
self.win.mvwin(self.y, self.x)
|
||||
|
||||
def draw(self, msg: Optional[str] = None) -> None:
|
||||
self.win.clear()
|
||||
|
@ -125,14 +127,15 @@ class StatusView:
|
|||
|
||||
class ChatView:
|
||||
def __init__(self, stdscr: window, p: float = 0.5) -> None:
|
||||
self.stdscr = stdscr
|
||||
self.h = 0
|
||||
self.w = 0
|
||||
self.win = stdscr.subwin(self.h, self.w, 0, 0)
|
||||
self._refresh = self.win.refresh
|
||||
|
||||
def resize(self, p: float = 0.25) -> None:
|
||||
self.h = curses.LINES - 1
|
||||
self.w = round(curses.COLS * p)
|
||||
def resize(self, rows: int, cols: int, p: float = 0.25) -> None:
|
||||
self.h = rows - 1
|
||||
self.w = round(cols * p)
|
||||
self.win.resize(self.h, self.w)
|
||||
|
||||
def _msg_color(self, is_selected: bool = False) -> int:
|
||||
|
@ -174,21 +177,16 @@ class ChatView:
|
|||
for attr, elem in zip(
|
||||
self._chat_attributes(is_selected), [f"{date} ", title]
|
||||
):
|
||||
if offset > self.w:
|
||||
break
|
||||
self.win.addstr(
|
||||
i,
|
||||
offset,
|
||||
truncate_to_len(elem, self.w - offset - 1),
|
||||
truncate_to_len(elem, max(0, self.w - offset - 1)),
|
||||
attr,
|
||||
)
|
||||
offset += len(elem)
|
||||
|
||||
if offset >= self.w:
|
||||
break
|
||||
|
||||
last_msg = " " + last_msg.replace("\n", " ")
|
||||
last_msg = truncate_to_len(last_msg, self.w - offset)
|
||||
last_msg = truncate_to_len(last_msg, max(0, self.w - offset))
|
||||
if last_msg.strip():
|
||||
self.win.addstr(
|
||||
i, offset, last_msg, self._msg_color(is_selected)
|
||||
|
@ -238,10 +236,10 @@ class MsgView:
|
|||
self.win = self.stdscr.subwin(self.h, self.w, 0, self.x)
|
||||
self._refresh = self.win.refresh
|
||||
|
||||
def resize(self, p: float = 0.5) -> None:
|
||||
self.h = curses.LINES - 1
|
||||
self.w = round(curses.COLS * p)
|
||||
self.x = curses.COLS - self.w
|
||||
def resize(self, rows: int, cols: int, p: float = 0.5) -> None:
|
||||
self.h = rows - 1
|
||||
self.w = round(cols * p)
|
||||
self.x = cols - self.w
|
||||
self.win.resize(self.h, self.w)
|
||||
self.win.mvwin(0, self.x)
|
||||
|
||||
|
|
Loading…
Reference in a new issue