Module sbx.ui.editor
Built in editor
Expand source code
"""
Built in editor
"""
from typing import Optional
from prompt_toolkit.application import Application
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding.bindings.focus import (
focus_next,
focus_previous,
)
from prompt_toolkit.key_binding.vi_state import InputMode
from prompt_toolkit.layout import HSplit, VSplit
from prompt_toolkit.styles import Style, style_from_pygments_cls
from prompt_toolkit.widgets import Label
from pygments.styles import get_style_by_name
from sbx.core.card import Card
from sbx.core.utility import simplify_path
from sbx.ui.controls import BaseUi, MarkdownArea
MARKDOWN_STYLE = style_from_pygments_cls(get_style_by_name("vim"))
TITLE = "---- SBX - Flashcards ----"
VI_STATUS_CELL = 3
PATH_CELL = 1
class EditorInterface(BaseUi):
"""
UI class for built in editor
"""
def __init__(self, card: Optional[Card]):
super().__init__()
self._card = card
path = "-"
if card:
path = simplify_path(card.path)
self._label_text_parts = [
"SBX",
path,
"Press F1 for help",
"--NAVIGATION--",
]
self.layout = self.create_root_layout(
container=self._get_base_layout(),
focused_element=self.text_area_front,
)
self.create_key_bindings()
self.application = Application(
layout=self.layout,
key_bindings=self.kb,
style=self._get_style(),
full_screen=True,
editing_mode=EditingMode.VI,
before_render=self.before_render,
paste_mode=False,
)
self._saved = False
def _get_style(self):
return Style(
[
("status", "bg:ansigreen ansiblack"),
("button", "ansiblack"),
("button-arrow", "ansiblack"),
("button focused", "bg:ansired"),
("main-panel", "bg:ansiblack"),
]
+ MARKDOWN_STYLE.style_rules
)
def _get_base_layout(self):
self.text_area_front = MarkdownArea()
self.text_area_front.text = self._card.front
self.text_area_back = MarkdownArea()
self.text_area_back.text = self._card.back
self.label = Label(text=self._get_label_text, style="class:status")
root_container = HSplit(
[
VSplit(
[
HSplit(
[
Label(
text="[Flashcard Front]",
style="class:status",
),
self.text_area_front,
]
),
HSplit(
[
Label(
text="[Flashcard Back]",
style="class:status",
),
self.text_area_back,
]
),
]
),
self.label,
],
style="class:main-panel",
)
return root_container
def before_render(self, app: Application):
if app.vi_state.input_mode == InputMode.NAVIGATION:
self._label_text_parts[VI_STATUS_CELL] = "--NAVIGATION--"
if app.vi_state.input_mode == InputMode.INSERT_MULTIPLE:
self._label_text_parts[VI_STATUS_CELL] = "--INSERT (MULTI)--"
if app.vi_state.input_mode == InputMode.REPLACE:
self._label_text_parts[VI_STATUS_CELL] = "--REPLACE--"
if app.vi_state.input_mode == InputMode.INSERT:
self._label_text_parts[VI_STATUS_CELL] = "--INSERT--"
if app.vi_state.recording_register is not None:
self._label_text_parts[VI_STATUS_CELL] = "recording..."
def _get_label_text(self):
return " | ".join(self._label_text_parts)
def _indent(self, _):
text = self._current_text_area()
if text:
text.indent()
def _save(self, _):
error_message = """
Failed to save file - {!r}
--------
{}
"""
self._saved = False
front, back = self.text_area_front.text, self.text_area_back.text
if not front:
self.message_box(TITLE, "Card front cannot be empty")
return
if not back:
self.message_box(TITLE, "Card back cannot be empty")
return
self._card.front = front
self._card.back = back
try:
self._card.save()
except (IOError, OSError) as ex:
self.message_box(
TITLE, error_message.format(self._card.path, str(ex))
)
return
self._saved = True
def _current_text_area(self):
if not self.layout.buffer_has_focus:
return None
buffer = self.layout.current_control
if id(buffer) == id(self.text_area_front.control):
return self.text_area_front
elif id(buffer) == id(self.text_area_back.control):
return self.text_area_back
return None
def _navigation(self, _):
self.application.vi_state.input_mode = InputMode.NAVIGATION
def _help(self, _):
message = """
Main UI
-----------
Control+e - Exit
Control+w - Write/Save
Control+Left - Focus Previous
Control+Up - Focus Previous
Control+Right - Focus Next
Control+Down - Focus Next
Control+d - Display Card Stat/Meta Data
Editor
-----------
Escape - Enter Normal Mode (Vi)
i - Enter Insert Mode
* We start in Insert mode
* In Normal mode you can use `i` to go
| back in to insert mode
* Macros & other Vi features
| in prompt-toolkit are supported
""".strip()
message = "\n".join([x.strip() for x in message.splitlines()])
self.message_box(TITLE, message)
def _exit(self, _):
message = """
Do you want to save before exit?
(You will loose changes if you select no)
File = {!r}
"""
front, back = self.text_area_front.text, self.text_area_back.text
dirty = front != self._card.front or back != self._card.back
if dirty:
self.confirm_box(
TITLE,
message.format(self._card.path),
self._save_exit,
lambda: self.exit_clicked(None),
)
else:
self.exit_clicked(None)
def _save_exit(self):
self._save(None)
if self._saved:
self.exit_clicked(None)
def get_actions(self) -> dict:
return {
"indent": self._indent,
"exit": self._exit,
"next": focus_next,
"prev": focus_previous,
"navigation": self._navigation,
"save": self._save,
"help": self._help,
"info": self.display_info,
}
def get_keybindings(self) -> dict:
return {
"indent": "tab",
"exit": "c-e",
"next": "c-right,c-down",
"prev": "c-left,c-up",
"navigation": "escape",
"save": "c-w",
"help": "f1",
"info": "c-d",
}
def debug(self, x):
self.message_box("DEBUG", repr(x))
def get_current_app(self) -> Application:
return self.application
def get_current_layout(self):
return self.layout
def display_info(self, _=None):
message = self._get_stat().strip()
message = "\n".join([x.strip() for x in message.splitlines()])
self.message_box(TITLE, message)
def _get_stat(self):
return self._card.human_readable_info
def run(self):
# Start in the insertion mode
self.application.vi_state.input_mode = InputMode.INSERT
self.application.run()
Classes
class EditorInterface (card: Optional[Card])
-
UI class for built in editor
Expand source code
class EditorInterface(BaseUi): """ UI class for built in editor """ def __init__(self, card: Optional[Card]): super().__init__() self._card = card path = "-" if card: path = simplify_path(card.path) self._label_text_parts = [ "SBX", path, "Press F1 for help", "--NAVIGATION--", ] self.layout = self.create_root_layout( container=self._get_base_layout(), focused_element=self.text_area_front, ) self.create_key_bindings() self.application = Application( layout=self.layout, key_bindings=self.kb, style=self._get_style(), full_screen=True, editing_mode=EditingMode.VI, before_render=self.before_render, paste_mode=False, ) self._saved = False def _get_style(self): return Style( [ ("status", "bg:ansigreen ansiblack"), ("button", "ansiblack"), ("button-arrow", "ansiblack"), ("button focused", "bg:ansired"), ("main-panel", "bg:ansiblack"), ] + MARKDOWN_STYLE.style_rules ) def _get_base_layout(self): self.text_area_front = MarkdownArea() self.text_area_front.text = self._card.front self.text_area_back = MarkdownArea() self.text_area_back.text = self._card.back self.label = Label(text=self._get_label_text, style="class:status") root_container = HSplit( [ VSplit( [ HSplit( [ Label( text="[Flashcard Front]", style="class:status", ), self.text_area_front, ] ), HSplit( [ Label( text="[Flashcard Back]", style="class:status", ), self.text_area_back, ] ), ] ), self.label, ], style="class:main-panel", ) return root_container def before_render(self, app: Application): if app.vi_state.input_mode == InputMode.NAVIGATION: self._label_text_parts[VI_STATUS_CELL] = "--NAVIGATION--" if app.vi_state.input_mode == InputMode.INSERT_MULTIPLE: self._label_text_parts[VI_STATUS_CELL] = "--INSERT (MULTI)--" if app.vi_state.input_mode == InputMode.REPLACE: self._label_text_parts[VI_STATUS_CELL] = "--REPLACE--" if app.vi_state.input_mode == InputMode.INSERT: self._label_text_parts[VI_STATUS_CELL] = "--INSERT--" if app.vi_state.recording_register is not None: self._label_text_parts[VI_STATUS_CELL] = "recording..." def _get_label_text(self): return " | ".join(self._label_text_parts) def _indent(self, _): text = self._current_text_area() if text: text.indent() def _save(self, _): error_message = """ Failed to save file - {!r} -------- {} """ self._saved = False front, back = self.text_area_front.text, self.text_area_back.text if not front: self.message_box(TITLE, "Card front cannot be empty") return if not back: self.message_box(TITLE, "Card back cannot be empty") return self._card.front = front self._card.back = back try: self._card.save() except (IOError, OSError) as ex: self.message_box( TITLE, error_message.format(self._card.path, str(ex)) ) return self._saved = True def _current_text_area(self): if not self.layout.buffer_has_focus: return None buffer = self.layout.current_control if id(buffer) == id(self.text_area_front.control): return self.text_area_front elif id(buffer) == id(self.text_area_back.control): return self.text_area_back return None def _navigation(self, _): self.application.vi_state.input_mode = InputMode.NAVIGATION def _help(self, _): message = """ Main UI ----------- Control+e - Exit Control+w - Write/Save Control+Left - Focus Previous Control+Up - Focus Previous Control+Right - Focus Next Control+Down - Focus Next Control+d - Display Card Stat/Meta Data Editor ----------- Escape - Enter Normal Mode (Vi) i - Enter Insert Mode * We start in Insert mode * In Normal mode you can use `i` to go | back in to insert mode * Macros & other Vi features | in prompt-toolkit are supported """.strip() message = "\n".join([x.strip() for x in message.splitlines()]) self.message_box(TITLE, message) def _exit(self, _): message = """ Do you want to save before exit? (You will loose changes if you select no) File = {!r} """ front, back = self.text_area_front.text, self.text_area_back.text dirty = front != self._card.front or back != self._card.back if dirty: self.confirm_box( TITLE, message.format(self._card.path), self._save_exit, lambda: self.exit_clicked(None), ) else: self.exit_clicked(None) def _save_exit(self): self._save(None) if self._saved: self.exit_clicked(None) def get_actions(self) -> dict: return { "indent": self._indent, "exit": self._exit, "next": focus_next, "prev": focus_previous, "navigation": self._navigation, "save": self._save, "help": self._help, "info": self.display_info, } def get_keybindings(self) -> dict: return { "indent": "tab", "exit": "c-e", "next": "c-right,c-down", "prev": "c-left,c-up", "navigation": "escape", "save": "c-w", "help": "f1", "info": "c-d", } def debug(self, x): self.message_box("DEBUG", repr(x)) def get_current_app(self) -> Application: return self.application def get_current_layout(self): return self.layout def display_info(self, _=None): message = self._get_stat().strip() message = "\n".join([x.strip() for x in message.splitlines()]) self.message_box(TITLE, message) def _get_stat(self): return self._card.human_readable_info def run(self): # Start in the insertion mode self.application.vi_state.input_mode = InputMode.INSERT self.application.run()
Ancestors
Subclasses
Methods
def before_render(self, app: prompt_toolkit.application.application.Application)
-
Expand source code
def before_render(self, app: Application): if app.vi_state.input_mode == InputMode.NAVIGATION: self._label_text_parts[VI_STATUS_CELL] = "--NAVIGATION--" if app.vi_state.input_mode == InputMode.INSERT_MULTIPLE: self._label_text_parts[VI_STATUS_CELL] = "--INSERT (MULTI)--" if app.vi_state.input_mode == InputMode.REPLACE: self._label_text_parts[VI_STATUS_CELL] = "--REPLACE--" if app.vi_state.input_mode == InputMode.INSERT: self._label_text_parts[VI_STATUS_CELL] = "--INSERT--" if app.vi_state.recording_register is not None: self._label_text_parts[VI_STATUS_CELL] = "recording..."
def debug(self, x)
-
Expand source code
def debug(self, x): self.message_box("DEBUG", repr(x))
def display_info(self)
-
Expand source code
def display_info(self, _=None): message = self._get_stat().strip() message = "\n".join([x.strip() for x in message.splitlines()]) self.message_box(TITLE, message)
def get_actions(self) ‑> dict
-
Expand source code
def get_actions(self) -> dict: return { "indent": self._indent, "exit": self._exit, "next": focus_next, "prev": focus_previous, "navigation": self._navigation, "save": self._save, "help": self._help, "info": self.display_info, }
def get_current_app(self) ‑> prompt_toolkit.application.application.Application
-
Expand source code
def get_current_app(self) -> Application: return self.application
def get_current_layout(self)
-
Expand source code
def get_current_layout(self): return self.layout
def get_keybindings(self) ‑> dict
-
Expand source code
def get_keybindings(self) -> dict: return { "indent": "tab", "exit": "c-e", "next": "c-right,c-down", "prev": "c-left,c-up", "navigation": "escape", "save": "c-w", "help": "f1", "info": "c-d", }
def run(self)
-
Expand source code
def run(self): # Start in the insertion mode self.application.vi_state.input_mode = InputMode.INSERT self.application.run()
Inherited members