Compare commits

...

5 commits

Author SHA1 Message Date
4a2eac58fe Add todos 2019-11-30 15:28:57 +00:00
19d72b3e67 Add demo gif to readme 2019-06-25 19:59:05 +00:00
1faee1b550 Stop using the dataclass module 2019-06-22 22:20:50 +00:00
6f0555c21b Fix indentation of multi-line messages 2019-06-22 21:14:08 +00:00
d445586c92 Clean up 2019-06-21 18:27:29 +00:00
7 changed files with 52 additions and 302 deletions

View file

@ -2,7 +2,9 @@
## Next version
Nothing yet
- Add demo gif to readme
- Fix indentation of multi-line messages
- Stop using dataclass (for backwards compatibility with Python 3.6)
## 1.0.0 (2019-06-21)

View file

@ -2,6 +2,8 @@
A TUI client for [euphoria.io](https://euphoria.io)
![bowl in action](demo.gif)
## Installation
Ensure that you have at least Python 3.7 installed.

View file

@ -1,4 +1,3 @@
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
@ -62,12 +61,17 @@ class Kind(Enum):
Condition = Callable[[Any], bool]
@dataclass
class Option:
kind: Kind
default: Any
conditions: Iterable[Tuple[Condition, str]] = field(default_factory=list)
def __init__(self,
kind: Kind,
default: Any,
conditions: Iterable[Tuple[Condition, str]] = frozenset(),
) -> None:
self.kind = kind
self.default = default
self.conditions = conditions
def check_valid(self, value: Any) -> None:
if not self.kind.matches(value):

View file

@ -123,7 +123,7 @@ class EuphRenderer(CursorRenderer):
right = AT(self._surround_right, attributes=self._surround_attrs)
nick_str = left + nick + right + AT(" ")
nick_spaces = AT(" " * len(nick))
nick_spaces = AT(" " * len(nick_str))
content = self._filter_unicode(message.content)
lines = []

BIN
demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

37
todo.txt Normal file
View file

@ -0,0 +1,37 @@
- config
x colors
- key bindings
- documentation (especially of the config)
- profiling/optimisation
- detail mode
- fold threads
- nick list
- better key bindings/controls
- center cursor on screen (after scrolling the view without scrolling the cursor)
- mouse support
- searching for messages
- better message editing when the screen is full
- detect when the dimensions are too small (meta width etc.) and display warning
- green "unread message" markers
- highlight things in messages
- offline log browsing
- @mentions
- &rooms
- https://links
- :emojis:
- /me s
- word wrapping for messages
- multi-room support
- db backend
- download room log
- auto repair gaps in log
x robust starting script
x install via pip from github
x runnable script
x parse command-line parameters
x nick list
x room_widget refactor
x save cookies

View file

@ -1,295 +0,0 @@
# Element supply of some sort
# Dict-/map-like
# Tree structure
# ↓
# List of already formatted elements
# Each with a line height, indentation, ...
# List structure
# ↓
# Messages and UI elements rendered to lines
# with meta-information, links/ids
# List structure, but on lines, not individual messages
class Element:
pass
class ElementSupply:
pass
class TreeDisplay:
"""
Message line coordinates:
n - Highest message
...
1 - Higher message
0 - Lowest message
Screen/line coordinates:
h-1 - First line
h-2 - Second line
...
1 - Second to last line
0 - Last line
Terms:
<base>
| ...
| ... (above)
| ...
| <stem>
| | ...
| | | ... | <anchor>
| ...
| ... (below)
| ...
or
<base>
| ...
| ... (above)
| ...
| <stem and anchor>
| ...
| ... (below)
| ...
The stem is a child of the base. The anchor is a direct or indirect child
of the stem, or it is the stem itself.
The base id may also be None (the implicit parent of all top-level
messages in a room)
"""
def __init__(self, window: Any) -> None:
self.window = window
self._anchor_id = None
# Position of the formatted anchor's uppermost line on the screen
self._anchor_screen_pos = 0
def render(self) -> None:
"""
Intermediate structures:
- Upwards and downwards list of elements + focused element
- Upwards and downwards list of rendered elements + focused element
- List of visible lines (saved and used for mouse clicks etc.)
Steps of rendering:
1. Load all necessary elements
2. Render all messages with indentation
3. Compile lines
Steps of advanced rendering:
1. Load focused element + render
2. Load tree of focused element + render
3. Load trees above and below + render, as many as necessary
4. While loading and rendering trees, auto-collapse
5. Move focus if focused element was hidden in an auto-collapse
...?
"""
# Step 1: Find and render the tree the anchor is in.
stem_id = self._supply.find_stem_id(self._anchor_id,
base=self._base_id)
tree = self._supply.get_tree(stem_id)
# The render might modify self._anchor_id, if the original anchor can't
# be displayed.
self._render_tree(tree)
above, anchor, below = self._split_at_anchor(tree)
# Step 2: Add more trees above and below, until the screen can be
# filled or there aren't any elements left in the store.
# h_win = 7
# 6 | <- h_above = 3
# 5 |
# 4 |
# 3 | <- anchor, self._anchor_screen_pos = 3, anchor.height = 2
# 2 |
# 1 | <- h_below = 2
# 0 |
#
# 7 - 3 - 1 = 3 -- correct
# 3 - 2 + 1 = 2 -- correct
height_window = None # TODO
# All values are converted to zero indexed values in the calculations
height_above = (height_window - 1) - self._anchor_screen_pos
height_below = self._anchor_screen_pos - (anchor.height - 1)
self._extend(above, height_above, base=self._base_id)
self._extend(below, height_below, base=self._base_id)
self._lines = self._render_to_lines(above, anchor, below)
self._update_window(self._lines)
# TreeDisplay plan(s):
#
# [ ] 1. Render a basic tree thing from an ElementSupply
# [ ] 1.1. Take/own a curses window
# [ ] 1.2. Keep track of the text internally
# [ ] 1.3. Retrieve elements from an ElementSupply
# [ ] 1.4. Render elements to text depending on width of curses window
# [ ] 1.5. Do indentation
# [ ] 1.6. Use "..." where a thing can't be rendered
# [ ] 2. Scroll and anchor to messages
# [ ] 2.1. Listen to key presses
# [ ] 2.2. Scroll up/down single lines
# [ ] 2.3. Render starting from the anchor
# [ ] 2.4. Some sort of culling, but preserve CONSISTENCY!
# [ ] 3. Focus on single message
# [ ] 3.1. Keep track of focused message
# [ ] 3.2. Move focused message
# [ ] 3.3. Keep message visible on screen
# [ ] 3.4. Set anchor to focus when focus is visible
# [ ] 3.5. Find anchor solution for when focus is offscreen
# [ ] 4. Collapse element threads
# [ ] 4.1. Collapse thread at any element
# [ ] 4.2. Auto-collapse threads when they can't be displayed
# [ ] 4.3. Focus collapsed messages
# [ ] 4.4. Move focus when a hidden message would have focus
# [ ] 5. Interaction with elements
# [ ] 5.1. Forward key presses
# [ ] 5.2. Mouse clicks + message attributes
# [ ] 5.3. Element visibility
# [ ] 5.4. Request more elements when the top of the element supply is hit
# [ ] 5.5. ... and various other things
# STRUCTURE
#
# No async!
#
# The TreeView "owns" and completely fills one curses window.
#
# When rendering things, the TreeDisplay takes elements from the ElementSupply
# as needed. This should be a fast operation.
#
# When receiving key presses, the ones that are not interpreted by the TreeView
# are passed onto the currently focused element (if any).
#
# When receiving mouse clicks, the clicked-on element is focused and then the
# click and attributes of the clicked character are passed onto the focused
# element.
#
# (TODO: Notify focused elements? Make "viewed/new" state for elements
# possible?)
#
#
#
# DESIGN PRINCIPLES
#
# Layout: See below
#
# Color support: Definitely.
# No-color-mode: Not planned.
# => Colors required.
# The tree display can display a tree-like structure of elements.
#
# Each element consists of:
# 1. a way to display the element
# 2. a way to forward key presses to the element
# 3. element-specific attributes (Attributes), including:
# 3.1 "id", the element's hashable id
# 3.2 optionally "parent_id", the element's parent's hashable id
#
# (TODO: A way to notify the element that it is visible?)
#
# (TODO: Jump to unread messages, mark messages as read, up/down arrows like
# instant, message jump tags?)
#
# (TODO: Curses + threading/interaction etc.?)
#
#
#
# LAYOUT
#
# A tree display's basic structure is something like this:
#
# <element>
# | <element>
# | | <element>
# | | <element>
# | <element>
# | <element>
# | | <element>
# | | | <element>
# | <element>
# <element>
# | <element>
#
# It has an indentation string ("| " in the above example) that is prepended to
# each line according to its indentation. (TODO: Possibly add different types
# of indentation strings?)
#
# In general, "..." is inserted any time a message or other placeholder can't
# be displayed. (TODO: If "..." can't be displayed, it is shortened until it
# can be displayed?)
#
# Elements can be collapsed. Collapsed elements are displayed as "+ (<n>)"
# where <n> is the number of elements in the hidden subtree.
#
# If an element is so far right that it can't be displayed, the tree display
# first tries to collapse the tree. If the collapsed message can't be displayed
# either, it uses "..." as mentioned above.
#
# <element>
# | <element>
# | | <element>
# | | <element>
# | <element>
# | + (3)
# | <element>
# <element>
# | <element>
# | | <element>
# | | | <element>
# | | | | ...
#
#
#
# NAVIGATION
#
# For navigation, the following movements/keys are used (all other key presses
# are forwarded to the currently selected element, if there is one):
#
# LEFT (left arrow, h): move to the selected element's parent
#
# RIGHT (right arrow, l): move to the selected element's first child
#
# UP (up arrow, k): move to the element visually below the selected element
#
# DOWN (down arrow, j): move to the element visually above the selected element
#
# Mod + LEFT: move to the selected element's previous sibling, if one exists
#
# Mod + RIGHT: move to the selected element's next sibling, if one exists
#
# Mod + UP: scroll up by scroll step
#
# Mod + DOWN: scroll down by scroll step
#
#
# CURSES
#
# Main thread:
# - async
# - yaboli
# - curses: non-blocking input
# - curses: update visuals
# - run editor in async variant of subprocess or separate thread
#
#
#
# STRUCTURE
#
# ???