Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a2eac58fe | |||
| 19d72b3e67 | |||
| 1faee1b550 | |||
| 6f0555c21b | |||
| d445586c92 |
7 changed files with 52 additions and 302 deletions
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
## Next version
|
## 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)
|
## 1.0.0 (2019-06-21)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
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,4 +1,3 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -62,12 +61,17 @@ class Kind(Enum):
|
||||||
|
|
||||||
Condition = Callable[[Any], bool]
|
Condition = Callable[[Any], bool]
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Option:
|
class Option:
|
||||||
|
|
||||||
kind: Kind
|
def __init__(self,
|
||||||
default: Any
|
kind: Kind,
|
||||||
conditions: Iterable[Tuple[Condition, str]] = field(default_factory=list)
|
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:
|
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))
|
nick_spaces = AT(" " * len(nick_str))
|
||||||
|
|
||||||
content = self._filter_unicode(message.content)
|
content = self._filter_unicode(message.content)
|
||||||
lines = []
|
lines = []
|
||||||
|
|
|
||||||
BIN
demo.gif
Normal file
BIN
demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 786 KiB |
37
todo.txt
Normal file
37
todo.txt
Normal 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
|
||||||
295
tree_display.py
295
tree_display.py
|
|
@ -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
|
|
||||||
#
|
|
||||||
# ???
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue