Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
7 changed files with 302 additions and 52 deletions
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
## Next version
|
## Next version
|
||||||
|
|
||||||
- Add demo gif to readme
|
Nothing yet
|
||||||
- Fix indentation of multi-line messages
|
|
||||||
- Stop using dataclass (for backwards compatibility with Python 3.6)
|
|
||||||
|
|
||||||
## 1.0.0 (2019-06-21)
|
## 1.0.0 (2019-06-21)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
A TUI client for [euphoria.io](https://euphoria.io)
|
A TUI client for [euphoria.io](https://euphoria.io)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Ensure that you have at least Python 3.7 installed.
|
Ensure that you have at least Python 3.7 installed.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
@ -61,17 +62,12 @@ class Kind(Enum):
|
||||||
|
|
||||||
Condition = Callable[[Any], bool]
|
Condition = Callable[[Any], bool]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Option:
|
class Option:
|
||||||
|
|
||||||
def __init__(self,
|
kind: Kind
|
||||||
kind: Kind,
|
default: Any
|
||||||
default: Any,
|
conditions: Iterable[Tuple[Condition, str]] = field(default_factory=list)
|
||||||
conditions: Iterable[Tuple[Condition, str]] = frozenset(),
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
self.kind = kind
|
|
||||||
self.default = default
|
|
||||||
self.conditions = conditions
|
|
||||||
|
|
||||||
def check_valid(self, value: Any) -> None:
|
def check_valid(self, value: Any) -> None:
|
||||||
if not self.kind.matches(value):
|
if not self.kind.matches(value):
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class EuphRenderer(CursorRenderer):
|
||||||
right = AT(self._surround_right, attributes=self._surround_attrs)
|
right = AT(self._surround_right, attributes=self._surround_attrs)
|
||||||
|
|
||||||
nick_str = left + nick + right + AT(" ")
|
nick_str = left + nick + right + AT(" ")
|
||||||
nick_spaces = AT(" " * len(nick_str))
|
nick_spaces = AT(" " * len(nick))
|
||||||
|
|
||||||
content = self._filter_unicode(message.content)
|
content = self._filter_unicode(message.content)
|
||||||
lines = []
|
lines = []
|
||||||
|
|
|
||||||
BIN
demo.gif
BIN
demo.gif
Binary file not shown.
|
Before Width: | Height: | Size: 786 KiB |
37
todo.txt
37
todo.txt
|
|
@ -1,37 +0,0 @@
|
||||||
- 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
|
|
||||||
295
tree_display.py
Normal file
295
tree_display.py
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# ???
|
||||||
Loading…
Add table
Add a link
Reference in a new issue