Satisfy mypy and (re-)move files
This commit is contained in:
parent
ef5320058c
commit
2e56b1b925
10 changed files with 209 additions and 351 deletions
|
|
@ -1,8 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from .markup import *
|
||||
from .message import *
|
||||
|
||||
__all__: List[str] = []
|
||||
__all__ += markup.__all__
|
||||
__all__ += message.__all__
|
||||
4
cheuph/exceptions.py
Normal file
4
cheuph/exceptions.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
__all__ = ["RenderException"]
|
||||
|
||||
class RenderException(Exception):
|
||||
pass
|
||||
285
cheuph/markup.py
285
cheuph/markup.py
|
|
@ -1,285 +0,0 @@
|
|||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
__all__ = ["Attributes", "Chunk", "AttributedText"]
|
||||
|
||||
Attributes = Dict[str, Any]
|
||||
|
||||
|
||||
class Chunk:
|
||||
@staticmethod
|
||||
def join_chunks(chunks: List["Chunk"]) -> List["Chunk"]:
|
||||
if not chunks:
|
||||
return []
|
||||
|
||||
new_chunks: List[Chunk] = []
|
||||
|
||||
current_chunk = chunks[0]
|
||||
for chunk in chunks[1:]:
|
||||
joined_chunk = current_chunk._join(chunk)
|
||||
|
||||
if joined_chunk is None:
|
||||
new_chunks.append(current_chunk)
|
||||
current_chunk = chunk
|
||||
else:
|
||||
current_chunk = joined_chunk
|
||||
|
||||
new_chunks.append(current_chunk)
|
||||
|
||||
return new_chunks
|
||||
|
||||
# Common special methods
|
||||
|
||||
def __init__(self,
|
||||
text: str,
|
||||
attributes: Attributes = {},
|
||||
) -> None:
|
||||
self._text = text
|
||||
self._attributes = dict(attributes)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Chunk({self.text!r}, {self._attributes!r})"
|
||||
|
||||
# Uncommon special methods
|
||||
|
||||
def __getitem__(self, key: Union[int, slice]) -> "Chunk":
|
||||
return Chunk(self.text[key], self._attributes)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.text)
|
||||
|
||||
# Properties
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
return self._text
|
||||
|
||||
@property
|
||||
def attributes(self) -> Attributes:
|
||||
return dict(self._attributes)
|
||||
|
||||
# Private methods
|
||||
|
||||
def _join(self, chunk: "Chunk") -> Optional["Chunk"]:
|
||||
if self._attributes == chunk._attributes:
|
||||
return Chunk(self.text + chunk.text, self._attributes)
|
||||
|
||||
return None
|
||||
|
||||
# Public methods
|
||||
|
||||
def get(self, name: str, default: Any = None) -> Any:
|
||||
return self.attributes.get(name, default)
|
||||
|
||||
def set(self, name: str, value: Any) -> "Chunk":
|
||||
new_attributes = dict(self._attributes)
|
||||
new_attributes[name] = value
|
||||
return Chunk(self.text, new_attributes)
|
||||
|
||||
def remove(self, name: str) -> "Chunk":
|
||||
new_attributes = dict(self._attributes)
|
||||
|
||||
# This removes the value with that key, if it exists, and does nothing
|
||||
# if it doesn't exist. (Since we give a default value, no KeyError is
|
||||
# raised if the key isn't found.)
|
||||
new_attributes.pop(name, None)
|
||||
|
||||
return Chunk(self.text, new_attributes)
|
||||
|
||||
|
||||
class AttributedText:
|
||||
"""
|
||||
Objects of this class are immutable and behave str-like. Supported
|
||||
operations are len, + and splicing.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_chunks(cls, chunks: Iterable[Chunk]) -> "AttributedText":
|
||||
new = cls()
|
||||
new._chunks = Chunk.join_chunks(list(chunks))
|
||||
return new
|
||||
|
||||
# Common special methods
|
||||
|
||||
def __init__(self, text: Optional[str] = None) -> None:
|
||||
self._chunks: List[Chunk] = []
|
||||
if text is not None:
|
||||
self._chunks.append(Chunk(text))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"AttributedText.from_chunks({self._chunks!r})"
|
||||
|
||||
# Uncommon special methods
|
||||
|
||||
def __add__(self, other: "AttributedText") -> "AttributedText":
|
||||
return AttributedText.from_chunks(self._chunks + other._chunks)
|
||||
|
||||
def __getitem__(self, key: Union[int, slice]) -> "AttributedText":
|
||||
chunks: List[Chunk]
|
||||
|
||||
if isinstance(key, slice):
|
||||
chunks = Chunk.join_chunks(self._slice(key))
|
||||
else:
|
||||
chunks = [self._at(key)]
|
||||
|
||||
return AttributedText.from_chunks(chunks)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(map(len, self._chunks))
|
||||
|
||||
# Properties
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
return "".join(chunk.text for chunk in self._chunks)
|
||||
|
||||
@property
|
||||
def chunks(self) -> List[Chunk]:
|
||||
return list(self._chunks)
|
||||
|
||||
# Private methods
|
||||
|
||||
def _at(self, key: int) -> Chunk:
|
||||
if key < 0:
|
||||
key = len(self) + key
|
||||
|
||||
pos = 0
|
||||
for chunk in self._chunks:
|
||||
chunk_key = key - pos
|
||||
|
||||
if 0 <= chunk_key < len(chunk):
|
||||
return chunk[chunk_key]
|
||||
|
||||
pos += len(chunk)
|
||||
|
||||
# We haven't found the chunk
|
||||
raise KeyError
|
||||
|
||||
def _slice(self, key: slice) -> List[Chunk]:
|
||||
start, stop, step = key.start, key.stop, key.step
|
||||
|
||||
if start is None:
|
||||
start = 0
|
||||
elif start < 0:
|
||||
start = len(self) + start
|
||||
|
||||
if stop is None:
|
||||
stop = len(self)
|
||||
elif stop < 0:
|
||||
stop = len(self) + stop
|
||||
|
||||
pos = 0 # cursor position
|
||||
resulting_chunks = []
|
||||
|
||||
for chunk in self._chunks:
|
||||
chunk_start = start - pos
|
||||
chunk_stop = stop - pos
|
||||
|
||||
offset: Optional[int] = None
|
||||
if step is not None:
|
||||
offset = (start - pos) % step
|
||||
|
||||
if chunk_stop <= 0 or chunk_start >= len(chunk):
|
||||
pass
|
||||
elif chunk_start < 0 and chunk_stop > len(chunk):
|
||||
resulting_chunks.append(chunk[offset::step])
|
||||
elif chunk_start < 0:
|
||||
resulting_chunks.append(chunk[offset:chunk_stop:step])
|
||||
elif chunk_stop > len(chunk):
|
||||
resulting_chunks.append(chunk[chunk_start::step])
|
||||
else:
|
||||
resulting_chunks.append(chunk[chunk_start:chunk_stop:step])
|
||||
|
||||
pos += len(chunk)
|
||||
|
||||
return resulting_chunks
|
||||
|
||||
# Public methods
|
||||
|
||||
def at(self, pos: int) -> Attributes:
|
||||
return self._at(pos).attributes
|
||||
|
||||
def get(self,
|
||||
pos: int,
|
||||
name: str,
|
||||
default: Any = None,
|
||||
) -> Any:
|
||||
return self._at(pos).get(name, default)
|
||||
|
||||
# "find all separate blocks with this property"
|
||||
def find_all(self, name: str) -> List[Tuple[Any, int, int]]:
|
||||
blocks = []
|
||||
pos = 0
|
||||
block = None
|
||||
|
||||
for chunk in self._chunks:
|
||||
if name in chunk.attributes:
|
||||
attribute = chunk.attributes[name]
|
||||
start = pos
|
||||
stop = pos + len(chunk)
|
||||
|
||||
if block is None:
|
||||
block = (attribute, start, stop)
|
||||
continue
|
||||
|
||||
block_attr, block_start, _ = block
|
||||
|
||||
if block_attr == attribute:
|
||||
block = (attribute, block_start, stop)
|
||||
else:
|
||||
blocks.append(block)
|
||||
block = (attribute, start, stop)
|
||||
|
||||
pos += len(chunk)
|
||||
|
||||
if block is not None:
|
||||
blocks.append(block)
|
||||
|
||||
return blocks
|
||||
|
||||
def set(self,
|
||||
name: str,
|
||||
value: Any,
|
||||
start: Optional[int] = None,
|
||||
stop: Optional[int] = None,
|
||||
) -> "AttributedText":
|
||||
if start is None and stop is None:
|
||||
chunks = (chunk.set(name, value) for chunk in self._chunks)
|
||||
return AttributedText.from_chunks(chunks)
|
||||
elif start is None:
|
||||
return self[:stop].set(name, value) + self[stop:]
|
||||
elif stop is None:
|
||||
return self[:start] + self[start:].set(name, value)
|
||||
elif start > stop:
|
||||
# set value everywhere BUT the specified interval
|
||||
return self.set(name, value, stop=stop).set(name, value, start=start)
|
||||
else:
|
||||
middle = self[start:stop].set(name, value)
|
||||
return self[:start] + middle + self[stop:]
|
||||
|
||||
def set_at(self, name: str, value: Any, pos: int) -> "AttributedText":
|
||||
return self.set(name, value, pos, pos)
|
||||
|
||||
def remove(self,
|
||||
name: str,
|
||||
start: Optional[int] = None,
|
||||
stop: Optional[int] = None,
|
||||
) -> "AttributedText":
|
||||
if start is None and stop is None:
|
||||
chunks = (chunk.remove(name) for chunk in self._chunks)
|
||||
return AttributedText.from_chunks(chunks)
|
||||
elif start is None:
|
||||
return self[:stop].remove(name) + self[stop:]
|
||||
elif stop is None:
|
||||
return self[:start] + self[start:].remove(name)
|
||||
elif start > stop:
|
||||
# remove value everywhere BUT the specified interval
|
||||
return self.remove(name, stop=stop).remove(name, start=start)
|
||||
else:
|
||||
middle = self[start:stop].remove(name)
|
||||
return self[:start] + middle + self[stop:]
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
from typing import Hashable, Optional
|
||||
|
||||
from .markup import AttributedText
|
||||
|
||||
__all__ = ["Message"]
|
||||
|
||||
|
||||
class Message:
|
||||
def __init__(self,
|
||||
message_id: Hashable,
|
||||
parent_id: Optional[Hashable],
|
||||
author: str,
|
||||
content: str,
|
||||
) -> None:
|
||||
self._message_id = message_id
|
||||
self._parent_id = parent_id
|
||||
self._author = author
|
||||
self._content = content
|
||||
|
||||
@property
|
||||
def message_id(self) -> Hashable:
|
||||
return self._message_id
|
||||
|
||||
@property
|
||||
def parent_id(self) -> Optional[Hashable]:
|
||||
return self._parent_id
|
||||
|
||||
@property
|
||||
def author(self) -> str:
|
||||
return self._author
|
||||
|
||||
@property
|
||||
def content(self) -> str:
|
||||
return self._content
|
||||
|
||||
def render_content(self) -> AttributedText:
|
||||
return AttributedText(self.content)
|
||||
10
cheuph/plan.txt
Normal file
10
cheuph/plan.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
General/generic features:
|
||||
|
||||
Text with attributes attached to the characters
|
||||
|
||||
Can be...
|
||||
- split
|
||||
- joined
|
||||
- converted to raw text or other formats
|
||||
|
||||
The attributes of certain characters can be read
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
from typing import List
|
||||
|
||||
from .element import *
|
||||
from .markup import *
|
||||
from .tree_display import *
|
||||
|
||||
__all__ = []
|
||||
__all__: List[str] = []
|
||||
__all__ += element.__all__
|
||||
__all__ += markup.__all__
|
||||
__all__ += tree_display.__all__
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import collections
|
||||
from typing import Any, Deque, Optional, Set
|
||||
from typing import Any, List, Optional, Set
|
||||
|
||||
from .element import ElementSupply, Id, RenderedElement
|
||||
from .element import Element, ElementSupply, Id, RenderedElement
|
||||
from .tree_list import TreeList
|
||||
|
||||
__all__ = ["TreeDisplay"]
|
||||
|
||||
|
|
@ -23,15 +24,15 @@ class TreeDisplay:
|
|||
|
||||
# Object references
|
||||
self._supply = supply
|
||||
self._rendered: Optional[TreeList]
|
||||
self._rendered: Optional[TreeList] = None
|
||||
self._folded: Set[Id] = set()
|
||||
|
||||
def resize(self, width: int, height: int):
|
||||
def resize(self, width: int, height: int) -> None:
|
||||
# TODO maybe empty _rendered/invalidate caches etc.?
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
# Steps:
|
||||
#
|
||||
# 1. Find and render anchor's branch to TreeList
|
||||
|
|
@ -67,7 +68,10 @@ class TreeDisplay:
|
|||
self._fill_screen_upwards()
|
||||
self._fill_screen_downwards()
|
||||
|
||||
def _render_tree(self, tree: Element, depth=0):
|
||||
def _render_tree(self,
|
||||
tree: Element,
|
||||
depth: int = 0
|
||||
) -> List[RenderedElement]:
|
||||
elements: List[RenderedElement] = []
|
||||
|
||||
highlighted = tree.id == self._cursor_id
|
||||
|
|
@ -82,7 +86,10 @@ class TreeDisplay:
|
|||
|
||||
return elements
|
||||
|
||||
def _fill_screen_upwards(self):
|
||||
def _fill_screen_upwards(self) -> None:
|
||||
if self._rendered is None:
|
||||
return # TODO
|
||||
|
||||
while True:
|
||||
if self._rendered.upper_offset <= 0:
|
||||
break
|
||||
|
|
@ -96,11 +103,14 @@ class TreeDisplay:
|
|||
above_tree = self._supply.get_tree(above_tree_id)
|
||||
self._rendered.add_above(self._render_tree(above_tree))
|
||||
|
||||
def _fill_screen_downwards(self):
|
||||
def _fill_screen_downwards(self) -> None:
|
||||
"""
|
||||
Eerily similar to _fill_screen_upwards()...
|
||||
"""
|
||||
|
||||
if self._rendered is None:
|
||||
return # TODO
|
||||
|
||||
while True:
|
||||
if self._rendered.lower_offset >= self._height - 1:
|
||||
break
|
||||
|
|
@ -114,7 +124,7 @@ class TreeDisplay:
|
|||
below_tree = self._supply.get_tree(below_tree_id)
|
||||
self._rendered.add_below(self._render_tree(below_tree))
|
||||
|
||||
def draw_to(self, window: Any):
|
||||
def draw_to(self, window: Any) -> None:
|
||||
pass
|
||||
|
||||
# Terminology:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import collections
|
||||
from typing import Deque, List
|
||||
|
||||
from .element import Id, RenderedElement
|
||||
|
||||
__all__ = ["TreeList"]
|
||||
|
|
@ -7,9 +10,7 @@ class TreeList:
|
|||
tree: List[RenderedElement],
|
||||
anchor_id: Id,
|
||||
) -> None:
|
||||
self._deque = collections.deque()
|
||||
|
||||
self._anchor_id = anchor_id
|
||||
self._deque: Deque = collections.deque()
|
||||
|
||||
# The offsets can be thought of as the index of a line relative to the
|
||||
# anchor's first line.
|
||||
|
|
@ -19,8 +20,8 @@ class TreeList:
|
|||
#
|
||||
# The lower offset is the index of the lowermost message's LAST line.
|
||||
# lower_offset >= 0.
|
||||
self._upper_offset: Int
|
||||
self._lower_offset: Int
|
||||
self._upper_offset: int
|
||||
self._lower_offset: int
|
||||
|
||||
# The upper and lower tree ids are the ids of the uppermost or
|
||||
# lowermost tree added to the TreeList. They can be used to request the
|
||||
|
|
@ -28,14 +29,14 @@ class TreeList:
|
|||
self._upper_tree_id: Id
|
||||
self._lower_tree_id: Id
|
||||
|
||||
self._add_first_tree(tree)
|
||||
self._add_first_tree(tree, anchor_id)
|
||||
|
||||
@property
|
||||
def upper_offset(self) -> Int:
|
||||
def upper_offset(self) -> int:
|
||||
return self._upper_offset
|
||||
|
||||
@property
|
||||
def lower_offset(self) -> Int:
|
||||
def lower_offset(self) -> int:
|
||||
return self._lower_offset
|
||||
|
||||
@property
|
||||
|
|
@ -46,7 +47,10 @@ class TreeList:
|
|||
def lower_tree_id(self) -> Id:
|
||||
return self._lower_tree_id
|
||||
|
||||
def _add_first_tree(self, tree: List[RenderedElement]) -> None:
|
||||
def _add_first_tree(self,
|
||||
tree: List[RenderedElement],
|
||||
anchor_id: Id
|
||||
) -> None:
|
||||
if len(tree) == 0:
|
||||
raise ValueError("The tree must contain at least one element")
|
||||
|
||||
|
|
@ -57,7 +61,7 @@ class TreeList:
|
|||
offset = 0
|
||||
found_anchor = False
|
||||
|
||||
for rendered in elements:
|
||||
for rendered in tree:
|
||||
if rendered.element.id == anchor_id:
|
||||
found_anchor = True
|
||||
self._upper_offset = -offset
|
||||
|
|
|
|||
33
cheuph/test.py
Normal file
33
cheuph/test.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import curses
|
||||
import subprocess
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
|
||||
def main(stdscr: Any) -> None:
|
||||
while True:
|
||||
key = stdscr.getkey()
|
||||
|
||||
if key in {"\x1b", "q"}:
|
||||
return
|
||||
|
||||
elif key == "e":
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
tmpfilename = tmpdirname + "/" + "tempfile"
|
||||
#stdscr.addstr(f"{curses.COLOR_PAIRS!r}\n")
|
||||
stdscr.addstr(f"{tmpdirname!r} | {tmpfilename!r}\n")
|
||||
|
||||
stdscr.getkey()
|
||||
|
||||
curses.endwin()
|
||||
subprocess.run(["nvim", tmpfilename])
|
||||
stdscr.refresh()
|
||||
|
||||
stdscr.getkey()
|
||||
|
||||
with open(tmpfilename) as f:
|
||||
for line in f:
|
||||
stdscr.addstr(line)
|
||||
|
||||
|
||||
curses.wrapper(main)
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
class Element:
|
||||
pass
|
||||
|
||||
class ElementSupply:
|
||||
pass
|
||||
|
||||
class TreeDisplay:
|
||||
pass
|
||||
|
||||
# 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