Initialization of NomadNet project
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import RNS
|
||||
import nomadnet
|
||||
|
||||
class GraphicalUI:
|
||||
|
||||
def __init__(self):
|
||||
RNS.log("Graphical UI not implemented", RNS.LOG_ERROR)
|
||||
nomadnet.panic()
|
||||
@@ -0,0 +1,8 @@
|
||||
import RNS
|
||||
import nomadnet
|
||||
|
||||
class MenuUI:
|
||||
|
||||
def __init__(self):
|
||||
RNS.log("Menu UI not implemented", RNS.LOG_ERROR, _override_destination=True)
|
||||
nomadnet.panic()
|
||||
@@ -0,0 +1,19 @@
|
||||
import RNS
|
||||
import nomadnet
|
||||
import time
|
||||
|
||||
from nomadnet import NomadNetworkApp
|
||||
|
||||
class NoneUI:
|
||||
|
||||
def __init__(self):
|
||||
self.app = NomadNetworkApp.get_shared_instance()
|
||||
self.app.ui = self
|
||||
|
||||
if not self.app.force_console_log:
|
||||
RNS.log("Nomad Network started in daemon mode, all further messages are logged to "+str(self.app.logfilepath), RNS.LOG_INFO, _override_destination=True)
|
||||
else:
|
||||
RNS.log("Nomad Network daemon started", RNS.LOG_INFO)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,269 @@
|
||||
import RNS
|
||||
import urwid
|
||||
import time
|
||||
import os
|
||||
import platform
|
||||
|
||||
import nomadnet
|
||||
from nomadnet.ui import THEME_DARK, THEME_LIGHT
|
||||
from nomadnet.ui.textui import *
|
||||
from nomadnet import NomadNetworkApp
|
||||
|
||||
COLORMODE_MONO = 1
|
||||
COLORMODE_16 = 16
|
||||
COLORMODE_88 = 88
|
||||
COLORMODE_256 = 256
|
||||
COLORMODE_TRUE = 2**24
|
||||
|
||||
THEMES = {
|
||||
THEME_DARK: {
|
||||
"urwid_theme": [
|
||||
# Style name # 16-color style # Monochrome style # 88, 256 and true-color style
|
||||
("heading", "light gray,underline", "default", "underline", "g93,underline", "default"),
|
||||
("menubar", "black", "light gray", "standout", "#111", "#bbb"),
|
||||
("scrollbar", "light gray", "default", "default", "#444", "default"),
|
||||
("shortcutbar", "black", "light gray", "standout", "#111", "#bbb"),
|
||||
("body_text", "light gray", "default", "default", "#ddd", "default"),
|
||||
("error_text", "dark red", "default", "default", "dark red", "default"),
|
||||
("warning_text", "yellow", "default", "default", "#ba4", "default"),
|
||||
("inactive_text", "dark gray", "default", "default", "dark gray", "default"),
|
||||
("browser_inactive", "dark gray", "default", "default", "#444", "default"),
|
||||
("buttons", "light green,bold", "default", "default", "#00a533", "default"),
|
||||
("msg_editor", "black", "light cyan", "standout", "#111", "#0bb"),
|
||||
("msg_header_ok", "black", "light green", "standout", "#111", "#6b2"),
|
||||
("msg_header_caution", "black", "yellow", "standout", "#111", "#fd3"),
|
||||
("msg_header_sent", "black", "light gray", "standout", "#111", "#ddd"),
|
||||
("msg_header_propagated", "black", "light blue", "standout", "#111", "#28b"),
|
||||
("msg_header_delivered", "black", "light blue", "standout", "#111", "#28b"),
|
||||
("msg_header_failed", "black", "dark gray", "standout", "#000", "#777"),
|
||||
("msg_warning_untrusted", "black", "dark red", "standout", "#111", "dark red"),
|
||||
("msg_notice_unread", "light blue", "default", "standout", "#28b", "default"),
|
||||
("msg_notice_caution", "yellow", "default", "standout", "#fd3", "default"),
|
||||
("list_focus", "black", "light gray", "standout", "#111", "#aaa"),
|
||||
("list_off_focus", "black", "dark gray", "standout", "#111", "#777"),
|
||||
("list_trusted", "dark green", "default", "default", "#6b2", "default"),
|
||||
("list_focus_trusted", "black", "light gray", "standout", "#150", "#aaa"),
|
||||
("list_unknown", "dark gray", "default", "default", "#bbb", "default"),
|
||||
("list_normal", "dark gray", "default", "default", "#bbb", "default"),
|
||||
("list_untrusted", "dark red", "default", "default", "#a22", "default"),
|
||||
("list_focus_untrusted", "black", "light gray", "standout", "#810", "#aaa"),
|
||||
("list_unresponsive", "yellow", "default", "default", "#b92", "default"),
|
||||
("list_focus_unresponsive", "black", "light gray", "standout", "#530", "#aaa"),
|
||||
("topic_list_normal", "light gray", "default", "default", "#ddd", "default"),
|
||||
("browser_controls", "light gray", "default", "default", "#bbb", "default"),
|
||||
("progress_full", "black", "light gray", "standout", "#111", "#bbb"),
|
||||
("progress_empty", "light gray", "default", "default", "#ddd", "default"),
|
||||
("interface_title", "", "", "default", "", ""),
|
||||
("interface_title_selected", "bold", "", "bold", "", ""),
|
||||
("connected_status", "dark green", "default", "default", "dark green", "default"),
|
||||
("disconnected_status", "dark red", "default", "default", "dark red", "default"),
|
||||
("placeholder", "dark gray", "default", "default", "dark gray", "default"),
|
||||
("placeholder_text", "dark gray", "default", "default", "dark gray", "default"),
|
||||
("error", "light red,blink", "default", "blink", "#f44,blink", "default"),
|
||||
("irc_ts", "dark gray", "default", "default", "#888", "default"),
|
||||
("irc_nick_self", "light green", "default", "default", "#6c5", "default"),
|
||||
("irc_nick_peer", "light cyan", "default", "default", "#3cd", "default"),
|
||||
("irc_notice", "yellow", "default", "default", "#fd3", "default"),
|
||||
("irc_error", "light red", "default", "default", "#f55", "default"),
|
||||
("irc_system", "dark gray", "default", "default", "#888", "default"),
|
||||
("irc_mention", "light red,bold", "default", "bold", "#fb4,bold", "default"),
|
||||
|
||||
],
|
||||
},
|
||||
THEME_LIGHT: {
|
||||
"urwid_theme": [
|
||||
# Style name # 16-color style # Monochrome style # 88, 256 and true-color style
|
||||
("heading", "dark gray,underline", "default", "underline", "g93,underline", "default"),
|
||||
("menubar", "black", "dark gray", "standout", "#111", "#bbb"),
|
||||
("scrollbar", "dark gray", "default", "default", "#444", "default"),
|
||||
("shortcutbar", "black", "dark gray", "standout", "#111", "#bbb"),
|
||||
("body_text", "dark gray", "default", "default", "#222", "default"),
|
||||
("error_text", "dark red", "default", "default", "dark red", "default"),
|
||||
("warning_text", "yellow", "default", "default", "#ba4", "default"),
|
||||
("inactive_text", "light gray", "default", "default", "dark gray", "default"),
|
||||
("buttons", "light green,bold", "default", "default", "#00a533", "default"),
|
||||
("msg_editor", "black", "dark cyan", "standout", "#111", "#0bb"),
|
||||
("msg_header_ok", "black", "dark green", "standout", "#111", "#6b2"),
|
||||
("msg_header_caution", "black", "yellow", "standout", "#111", "#fd3"),
|
||||
("msg_header_sent", "black", "dark gray", "standout", "#111", "#ddd"),
|
||||
("msg_header_propagated", "black", "light blue", "standout", "#111", "#28b"),
|
||||
("msg_header_delivered", "black", "light blue", "standout", "#111", "#28b"),
|
||||
("msg_header_failed", "black", "dark gray", "standout", "#000", "#777"),
|
||||
("msg_warning_untrusted", "black", "dark red", "standout", "#111", "dark red"),
|
||||
("msg_notice_unread", "dark blue", "default", "standout", "#069", "default"),
|
||||
("msg_notice_caution", "yellow", "default", "standout", "#fd3", "default"),
|
||||
("list_focus", "black", "dark gray", "standout", "#111", "#aaa"),
|
||||
("list_off_focus", "black", "dark gray", "standout", "#111", "#777"),
|
||||
("list_trusted", "dark green", "default", "default", "#4a0", "default"),
|
||||
("list_focus_trusted", "black", "dark gray", "standout", "#150", "#aaa"),
|
||||
("list_unknown", "dark gray", "default", "default", "#444", "default"),
|
||||
("list_normal", "dark gray", "default", "default", "#444", "default"),
|
||||
("list_untrusted", "dark red", "default", "default", "#a22", "default"),
|
||||
("list_focus_untrusted", "black", "dark gray", "standout", "#810", "#aaa"),
|
||||
("list_unresponsive", "yellow", "default", "default", "#b92", "default"),
|
||||
("list_focus_unresponsive", "black", "light gray", "standout", "#530", "#aaa"),
|
||||
("topic_list_normal", "dark gray", "default", "default", "#222", "default"),
|
||||
("browser_controls", "dark gray", "default", "default", "#444", "default"),
|
||||
("progress_full", "black", "dark gray", "standout", "#111", "#bbb"),
|
||||
("progress_empty", "dark gray", "default", "default", "#ddd", "default"),
|
||||
("interface_title", "dark gray", "default", "default", "#444", "default"),
|
||||
("interface_title_selected", "dark gray,bold", "default", "bold", "#444,bold", "default"),
|
||||
("connected_status", "dark green", "default", "default", "#4a0", "default"),
|
||||
("disconnected_status", "dark red", "default", "default", "#a22", "default"),
|
||||
("placeholder", "light gray", "default", "default", "#999", "default"),
|
||||
("placeholder_text", "light gray", "default", "default", "#999", "default"),
|
||||
("error", "dark red,blink", "default", "blink", "#a22,blink", "default"),
|
||||
("irc_ts", "dark gray", "default", "default", "#888", "default"),
|
||||
("irc_nick_self", "dark green", "default", "default", "#3a0", "default"),
|
||||
("irc_nick_peer", "dark cyan", "default", "default", "#077", "default"),
|
||||
("irc_notice", "brown", "default", "default", "#a70", "default"),
|
||||
("irc_error", "dark red", "default", "default", "#a22", "default"),
|
||||
("irc_system", "dark gray", "default", "default", "#888", "default"),
|
||||
("irc_mention", "dark red,bold", "default", "bold", "#c50,bold", "default"),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
GLYPHSETS = {
|
||||
"plain": 1,
|
||||
"unicode": 2,
|
||||
"nerdfont": 3
|
||||
}
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
urm_char = " \uf0e0"
|
||||
ur_char = "\uf0e0 "
|
||||
else:
|
||||
urm_char = " \uf003"
|
||||
ur_char = "\uf003 "
|
||||
|
||||
GLYPHS = {
|
||||
# Glyph name # Plain # Unicode # Nerd Font
|
||||
("check", "=", "\u2713", "\u2713"),
|
||||
("cross", "X", "\u2715", "\u2715"),
|
||||
("unknown", "?", "?", "?"),
|
||||
("encrypted", "", "\u26BF", "\uf023"),
|
||||
("plaintext", "!", "!", "\uf06e "),
|
||||
("arrow_r", "->", "\u2192", "\u2192"),
|
||||
("arrow_l", "<-", "\u2190", "\u2190"),
|
||||
("arrow_u", "/\\", "\u2191", "\u2191"),
|
||||
("arrow_d", "\\/", "\u2193", "\u2193"),
|
||||
("warning", "!", "\u26a0", "\uf12a"),
|
||||
("info", "i", "\u2139", "\U000f064e"),
|
||||
("unread", "[!]", "\u2709", ur_char),
|
||||
("divider1", "-", "\u2504", "\u2504"),
|
||||
("peer", "[P]", "\u24c5 ", "\uf415"),
|
||||
("node", "[N]", "\u24c3 ", "\U000f0002"),
|
||||
("page", "", "\u25a4 ", "\uf719 "),
|
||||
("speed", "", "\u25F7 ", "\U000f04c5 "),
|
||||
("decoration_menu", " +", " +", " \U000f043b"),
|
||||
("unread_menu", " !", " \u2709", urm_char),
|
||||
("globe", "", "", "\uf484"),
|
||||
("sent", "/\\", "\u2191", "\U000f0cd8"),
|
||||
("papermsg", "P", "\u25a4", "\uf719"),
|
||||
("qrcode", "QR", "\u25a4", "\uf029"),
|
||||
("selected", "[*] ", "\u25CF", "\u25CF"),
|
||||
("unselected", "[ ] ", "\u25CB", "\u25CB"),
|
||||
("file", "[F]", "\u25a4", "\uf15b"),
|
||||
("image", "[I]", "\u25a3", "\uf1c5"),
|
||||
("audio", "[~]", "\u266b", "\uf1c7"),
|
||||
("pin", "*", "\u2605", "\uf08d"),
|
||||
("copy", "[C]", "\u29c9", "\uf0c5"),
|
||||
}
|
||||
|
||||
class TextUI:
|
||||
|
||||
def __init__(self):
|
||||
self.restore_ixon = False
|
||||
|
||||
try:
|
||||
rval = os.system("stty -a | grep \"\\-ixon\"")
|
||||
if rval == 0:
|
||||
pass
|
||||
|
||||
else:
|
||||
os.system("stty -ixon")
|
||||
self.restore_ixon = True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not configure terminal flow control sequences, some keybindings may not work.", RNS.LOG_WARNING)
|
||||
RNS.log("The contained exception was: "+str(e))
|
||||
|
||||
self.app = NomadNetworkApp.get_shared_instance()
|
||||
self.app.ui = self
|
||||
self.loop = None
|
||||
|
||||
urwid.set_encoding("UTF-8")
|
||||
|
||||
intro_timeout = self.app.config["textui"]["intro_time"]
|
||||
colormode = self.app.config["textui"]["colormode"]
|
||||
theme = self.app.config["textui"]["theme"]
|
||||
mouse_enabled = self.app.config["textui"]["mouse_enabled"]
|
||||
|
||||
self.palette = THEMES[theme]["urwid_theme"]
|
||||
|
||||
if self.app.config["textui"]["glyphs"] == "plain":
|
||||
glyphset = "plain"
|
||||
elif self.app.config["textui"]["glyphs"] == "unicode":
|
||||
glyphset = "unicode"
|
||||
elif self.app.config["textui"]["glyphs"] == "nerdfont":
|
||||
glyphset = "nerdfont"
|
||||
else:
|
||||
glyphset = "unicode"
|
||||
|
||||
self.glyphs = {}
|
||||
for glyph in GLYPHS:
|
||||
self.glyphs[glyph[0]] = glyph[GLYPHSETS[glyphset]]
|
||||
|
||||
self.screen = urwid.raw_display.Screen()
|
||||
self.screen.register_palette(self.palette)
|
||||
|
||||
self.main_display = Main.MainDisplay(self, self.app)
|
||||
|
||||
if intro_timeout > 0:
|
||||
self.intro_display = Extras.IntroDisplay(self.app)
|
||||
initial_widget = self.intro_display.widget
|
||||
else:
|
||||
initial_widget = self.main_display.widget
|
||||
|
||||
self.loop = urwid.MainLoop(initial_widget, unhandled_input=self.unhandled_input, screen=self.screen, handle_mouse=mouse_enabled)
|
||||
|
||||
if intro_timeout > 0:
|
||||
self.loop.set_alarm_in(intro_timeout, self.display_main)
|
||||
|
||||
if "KONSOLE_VERSION" in os.environ:
|
||||
if colormode > 16:
|
||||
RNS.log("", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("You are using the terminal emulator Konsole.", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("If you are not seeing the user interface, it is due to a bug in Konsole/urwid.", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("", RNS.LOG_WARNING, _override_destination = True)
|
||||
|
||||
RNS.log("To circumvent this, use another terminal emulator, or launch nomadnet within a", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("screen session, using a command like the following:", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("screen nomadnet", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("", RNS.LOG_WARNING, _override_destination = True)
|
||||
RNS.log("Press ctrl-c to exit now and try again.", RNS.LOG_WARNING, _override_destination = True)
|
||||
|
||||
self.set_colormode(colormode)
|
||||
|
||||
self.main_display.start()
|
||||
self.loop.run()
|
||||
|
||||
def set_colormode(self, colormode):
|
||||
self.colormode = colormode
|
||||
self.screen.set_terminal_properties(colormode)
|
||||
|
||||
if self.colormode < 256:
|
||||
self.screen.reset_default_terminal_palette()
|
||||
self.restore_palette = True
|
||||
|
||||
def unhandled_input(self, key):
|
||||
if key == "ctrl q":
|
||||
raise urwid.ExitMainLoop
|
||||
elif key == "ctrl e":
|
||||
pass
|
||||
|
||||
def display_main(self, loop, user_data):
|
||||
self.loop.widget = self.main_display.widget
|
||||
@@ -0,0 +1,8 @@
|
||||
import RNS
|
||||
import nomadnet
|
||||
|
||||
class WebUI:
|
||||
|
||||
def __init__(self):
|
||||
RNS.log("Web UI not implemented", RNS.LOG_ERROR)
|
||||
nomadnet.panic()
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import glob
|
||||
import RNS
|
||||
import nomadnet
|
||||
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
THEME_DARK = 0x01
|
||||
THEME_LIGHT = 0x02
|
||||
|
||||
UI_NONE = 0x00
|
||||
UI_MENU = 0x01
|
||||
UI_TEXT = 0x02
|
||||
UI_GRAPHICAL = 0x03
|
||||
UI_WEB = 0x04
|
||||
UI_MODES = [UI_NONE, UI_MENU, UI_TEXT, UI_GRAPHICAL, UI_WEB]
|
||||
|
||||
def spawn(uimode):
|
||||
if uimode in UI_MODES:
|
||||
if uimode == UI_NONE:
|
||||
RNS.log("Starting Nomad Network daemon...", RNS.LOG_INFO)
|
||||
else:
|
||||
RNS.log("Starting user interface...", RNS.LOG_INFO)
|
||||
|
||||
if uimode == UI_MENU:
|
||||
from .MenuUI import MenuUI
|
||||
return MenuUI()
|
||||
elif uimode == UI_TEXT:
|
||||
from .TextUI import TextUI
|
||||
return TextUI()
|
||||
elif uimode == UI_GRAPHICAL:
|
||||
from .GraphicalUI import GraphicalUI
|
||||
return GraphicalUI()
|
||||
elif uimode == UI_WEB:
|
||||
from .WebUI import WebUI
|
||||
return WebUI()
|
||||
elif uimode == UI_NONE:
|
||||
from .NoneUI import NoneUI
|
||||
return NoneUI()
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
RNS.log("Invalid UI mode", RNS.LOG_ERROR, _override_destination=True)
|
||||
nomadnet.panic()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
import nomadnet
|
||||
import urwid
|
||||
import platform
|
||||
|
||||
class ConfigDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text(""), "shortcutbar")
|
||||
|
||||
class ConfigFiller(urwid.WidgetWrap):
|
||||
def __init__(self, widget, app):
|
||||
self.app = app
|
||||
self.filler = urwid.Filler(widget, urwid.TOP)
|
||||
super().__init__(self.filler)
|
||||
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == "up":
|
||||
self.app.ui.main_display.frame.focus_position = "header"
|
||||
|
||||
return super(ConfigFiller, self).keypress(size, key)
|
||||
|
||||
class ConfigDisplay():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
def open_editor(sender):
|
||||
self.editor_term = EditorTerminal(self.app, self)
|
||||
self.widget = urwid.LineBox(self.editor_term)
|
||||
self.app.ui.main_display.update_active_sub_display()
|
||||
self.app.ui.main_display.frame.focus_position = "body"
|
||||
self.editor_term.term.change_focus(True)
|
||||
|
||||
pile = urwid.Pile([
|
||||
urwid.Text(
|
||||
(
|
||||
"body_text",
|
||||
"\nTo change the configuration, edit the config file located at:\n\n"
|
||||
+self.app.configpath
|
||||
+"\n\nRestart Nomad Network for changes to take effect\n",
|
||||
),
|
||||
align=urwid.CENTER,
|
||||
),
|
||||
urwid.Padding(urwid.Button("Open Editor", on_press=open_editor), width=15, align=urwid.CENTER),
|
||||
])
|
||||
|
||||
self.config_explainer = ConfigFiller(pile, self.app)
|
||||
self.shortcuts_display = ConfigDisplayShortcuts(self.app)
|
||||
self.widget = self.config_explainer
|
||||
|
||||
def shortcuts(self):
|
||||
return self.shortcuts_display
|
||||
|
||||
class EditorTerminal(urwid.WidgetWrap):
|
||||
def __init__(self, app, parent):
|
||||
self.app = app
|
||||
self.parent = parent
|
||||
editor_cmd = self.app.config["textui"]["editor"]
|
||||
|
||||
# The "editor" alias is unavailable on Darwin,
|
||||
# so we replace it with nano.
|
||||
if platform.system() == "Darwin" and editor_cmd == "editor":
|
||||
editor_cmd = "nano"
|
||||
|
||||
self.term = urwid.Terminal(
|
||||
(editor_cmd, self.app.configpath),
|
||||
encoding='utf-8',
|
||||
main_loop=self.app.ui.loop,
|
||||
)
|
||||
|
||||
def quit_term(*args, **kwargs):
|
||||
self.parent.widget = self.parent.config_explainer
|
||||
self.app.ui.main_display.update_active_sub_display()
|
||||
self.app.ui.main_display.show_config(None)
|
||||
self.app.ui.main_display.request_redraw()
|
||||
|
||||
urwid.connect_signal(self.term, 'closed', quit_term)
|
||||
|
||||
super().__init__(self.term)
|
||||
|
||||
|
||||
def keypress(self, size, key):
|
||||
# TODO: Decide whether there should be a way to get out while editing
|
||||
#if key == "up":
|
||||
# nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
|
||||
return super(EditorTerminal, self).keypress(size, key)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
class DirectoryDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("Directory Display Shortcuts"), "shortcutbar")
|
||||
|
||||
class DirectoryDisplay():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
pile = urwid.Pile([
|
||||
urwid.Text(("body_text", "Directory Display \U0001F332")),
|
||||
])
|
||||
|
||||
self.shortcuts_display = DirectoryDisplayShortcuts(self.app)
|
||||
self.widget = urwid.Filler(pile, urwid.TOP)
|
||||
|
||||
def shortcuts(self):
|
||||
return self.shortcuts_display
|
||||
@@ -0,0 +1,19 @@
|
||||
class IntroDisplay():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
font = urwid.font.HalfBlock5x4Font()
|
||||
|
||||
big_text = urwid.BigText(("intro_title", self.app.config["textui"]["intro_text"]), font)
|
||||
big_text = urwid.Padding(big_text, align=urwid.CENTER, width=urwid.CLIP)
|
||||
|
||||
intro = urwid.Pile([
|
||||
big_text,
|
||||
urwid.Text(("Version %s" % (str(self.app.version))), align=urwid.CENTER),
|
||||
urwid.Divider(),
|
||||
urwid.Text(("-= Starting =- "), align=urwid.CENTER),
|
||||
])
|
||||
|
||||
self.widget = urwid.Filler(intro)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
import base64
|
||||
import urwid
|
||||
import RNS
|
||||
|
||||
|
||||
def osc52_copy(text):
|
||||
if not text:
|
||||
return False
|
||||
try:
|
||||
encoded = base64.b64encode(text.encode("utf-8")).decode("ascii")
|
||||
sys.stdout.write("\x1b]52;c;" + encoded + "\x07")
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
except Exception as e:
|
||||
RNS.log("Could not emit clipboard escape sequence: "+str(e), RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
|
||||
class ClickableIcon(urwid.Text):
|
||||
_selectable = False
|
||||
|
||||
def __init__(self, text, on_click=None):
|
||||
super().__init__(text)
|
||||
self._on_click = on_click
|
||||
|
||||
def mouse_event(self, size, event, button, x, y, focus):
|
||||
if button == 1 and "press" in event and self._on_click is not None:
|
||||
self._on_click()
|
||||
return True
|
||||
return False
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import sys
|
||||
import itertools
|
||||
import mmap
|
||||
import urwid
|
||||
import nomadnet
|
||||
|
||||
|
||||
class LogDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text(""), "shortcutbar")
|
||||
|
||||
|
||||
class LogDisplay():
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.shortcuts_display = LogDisplayShortcuts(self.app)
|
||||
self.widget = None
|
||||
|
||||
@property
|
||||
def log_term(self):
|
||||
return self.widget
|
||||
|
||||
def show(self):
|
||||
if self.widget is None:
|
||||
self.widget = log_widget(self.app)
|
||||
|
||||
def kill(self):
|
||||
if self.widget is not None:
|
||||
self.widget.terminate()
|
||||
self.widget = None
|
||||
|
||||
def shortcuts(self):
|
||||
return self.shortcuts_display
|
||||
|
||||
|
||||
class LogTerminal(urwid.WidgetWrap):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.log_term = urwid.Terminal(
|
||||
("tail", "-fn50", self.app.logfilepath),
|
||||
encoding='utf-8',
|
||||
escape_sequence="up",
|
||||
main_loop=self.app.ui.loop,
|
||||
)
|
||||
self.widget = urwid.LineBox(self.log_term)
|
||||
super().__init__(self.widget)
|
||||
|
||||
def terminate(self):
|
||||
self.log_term.terminate()
|
||||
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == "up":
|
||||
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
|
||||
|
||||
return super(LogTerminal, self).keypress(size, key)
|
||||
|
||||
|
||||
class LogTail(urwid.WidgetWrap):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.log_tail = urwid.Text(tail(self.app.logfilepath, 50))
|
||||
self.log = urwid.Scrollable(self.log_tail)
|
||||
self.log.set_scrollpos(-1)
|
||||
self.log_scrollbar = urwid.ScrollBar(self.log)
|
||||
# We have this here because ui.textui.Main depends on this field to kill it
|
||||
self.log_term = None
|
||||
|
||||
super().__init__(self.log_scrollbar)
|
||||
|
||||
def terminate(self):
|
||||
pass
|
||||
|
||||
|
||||
def log_widget(app, platform=sys.platform):
|
||||
if platform == "win32":
|
||||
return LogTail(app)
|
||||
else:
|
||||
return LogTerminal(app)
|
||||
|
||||
# https://stackoverflow.com/a/34029605/3713120
|
||||
def _tail(f_name, n, offset=0):
|
||||
def skip_back_lines(mm: mmap.mmap, numlines: int, startidx: int) -> int:
|
||||
'''Factored out to simplify handling of n and offset'''
|
||||
for _ in itertools.repeat(None, numlines):
|
||||
startidx = mm.rfind(b'\n', 0, startidx)
|
||||
if startidx < 0:
|
||||
break
|
||||
return startidx
|
||||
|
||||
# Open file in binary mode
|
||||
with open(f_name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
|
||||
# len(mm) - 1 handles files ending w/newline by getting the prior line
|
||||
startofline = skip_back_lines(mm, offset, len(mm) - 1)
|
||||
if startofline < 0:
|
||||
return [] # Offset lines consumed whole file, nothing to return
|
||||
# If using a generator function (yield-ing, see below),
|
||||
# this should be a plain return, no empty list
|
||||
|
||||
endoflines = startofline + 1 # Slice end to omit offset lines
|
||||
|
||||
# Find start of lines to capture (add 1 to move from newline to beginning of following line)
|
||||
startofline = skip_back_lines(mm, n, startofline) + 1
|
||||
|
||||
# Passing True to splitlines makes it return the list of lines without
|
||||
# removing the trailing newline (if any), so list mimics f.readlines()
|
||||
# return mm[startofline:endoflines].splitlines(True)
|
||||
# If Windows style \r\n newlines need to be normalized to \n
|
||||
return mm[startofline:endoflines].replace(os.linesep.encode(sys.getdefaultencoding()), b'\n').splitlines(True)
|
||||
|
||||
|
||||
def tail(f_name, n):
|
||||
"""
|
||||
Return the last n lines of a given file name, f_name.
|
||||
Akin to `tail -<n> <f_name>`
|
||||
"""
|
||||
def decode(b):
|
||||
return b.decode(encoding)
|
||||
|
||||
encoding = sys.getdefaultencoding()
|
||||
lines = map(decode, _tail(f_name=f_name, n=n))
|
||||
return ''.join(lines)
|
||||
@@ -0,0 +1,230 @@
|
||||
import RNS
|
||||
|
||||
from .Network import *
|
||||
from .Conversations import *
|
||||
from .Channels import *
|
||||
from .Directory import *
|
||||
from .Config import *
|
||||
from .Interfaces import *
|
||||
from .Map import *
|
||||
from .Log import *
|
||||
from .Guide import *
|
||||
import urwid
|
||||
|
||||
class SubDisplays():
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.network_display = NetworkDisplay(self.app)
|
||||
self.conversations_display = ConversationsDisplay(self.app)
|
||||
self.channels_display = ChannelsDisplay(self.app)
|
||||
self.directory_display = DirectoryDisplay(self.app)
|
||||
self.config_display = ConfigDisplay(self.app)
|
||||
self.interface_display = InterfaceDisplay(self.app)
|
||||
self.map_display = MapDisplay(self.app)
|
||||
self.log_display = LogDisplay(self.app)
|
||||
self.guide_display = GuideDisplay(self.app)
|
||||
|
||||
if app.firstrun:
|
||||
self.active_display = self.guide_display
|
||||
else:
|
||||
self.active_display = self.conversations_display
|
||||
|
||||
def active(self):
|
||||
return self.active_display
|
||||
|
||||
class MenuButton(urwid.Button):
|
||||
button_left = urwid.Text('[')
|
||||
button_right = urwid.Text(']')
|
||||
|
||||
class MainFrame(urwid.Frame):
|
||||
FOCUS_CHECK_TIMEOUT = 0.25
|
||||
|
||||
def __init__(self, body, header=None, footer=None, delegate=None):
|
||||
self.delegate = delegate
|
||||
self.current_focus = None
|
||||
super().__init__(body, header, footer)
|
||||
|
||||
def keypress_focus_check(self, deferred=False):
|
||||
current_focus = self.delegate.widget.get_focus_widgets()[-1]
|
||||
|
||||
if deferred:
|
||||
if current_focus != self.current_focus:
|
||||
self.focus_changed()
|
||||
else:
|
||||
def deferred_focus_check(loop, user_data):
|
||||
self.keypress_focus_check(deferred=True)
|
||||
self.delegate.app.ui.loop.set_alarm_in(MainFrame.FOCUS_CHECK_TIMEOUT, deferred_focus_check)
|
||||
|
||||
self.current_focus = current_focus
|
||||
|
||||
def focus_changed(self):
|
||||
current_focus = self.delegate.widget.get_focus_widgets()[-1]
|
||||
current_focus_path = self.delegate.widget.get_focus_path()
|
||||
|
||||
if len(current_focus_path) > 1:
|
||||
if current_focus_path[0] == "body":
|
||||
self.delegate.update_active_shortcuts()
|
||||
|
||||
if self.delegate.sub_displays.active() == self.delegate.sub_displays.conversations_display:
|
||||
# Needed to refresh indicativelistbox styles on mouse focus change
|
||||
self.delegate.sub_displays.conversations_display.focus_change_event()
|
||||
|
||||
def mouse_event(self, size, event, button, col, row, focus):
|
||||
current_focus = self.delegate.widget.get_focus_widgets()[-1]
|
||||
if current_focus != self.current_focus:
|
||||
self.focus_changed()
|
||||
|
||||
self.current_focus = current_focus
|
||||
return super(MainFrame, self).mouse_event(size, event, button, col, row, focus)
|
||||
|
||||
def keypress(self, size, key):
|
||||
self.keypress_focus_check()
|
||||
|
||||
#if key == "ctrl q":
|
||||
# raise urwid.ExitMainLoop
|
||||
|
||||
return super(MainFrame, self).keypress(size, key)
|
||||
|
||||
class MainDisplay():
|
||||
def __init__(self, ui, app):
|
||||
self.ui = ui
|
||||
self.app = app
|
||||
|
||||
self.menu_display = MenuDisplay(self.app, self)
|
||||
self.sub_displays = SubDisplays(self.app)
|
||||
|
||||
self.frame = MainFrame(self.sub_displays.active().widget, header=self.menu_display.widget, footer=self.sub_displays.active().shortcuts().widget, delegate=self)
|
||||
self.widget = self.frame
|
||||
|
||||
def show_network(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.network_display
|
||||
self.update_active_sub_display()
|
||||
self.sub_displays.network_display.start()
|
||||
|
||||
def show_conversations(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.conversations_display
|
||||
self.update_active_sub_display()
|
||||
|
||||
def show_channels(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.channels_display
|
||||
self.update_active_sub_display()
|
||||
self.sub_displays.channels_display.start()
|
||||
|
||||
def show_directory(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.directory_display
|
||||
self.update_active_sub_display()
|
||||
|
||||
def show_map(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.map_display
|
||||
self.update_active_sub_display()
|
||||
|
||||
def show_config(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.config_display
|
||||
self.update_active_sub_display()
|
||||
|
||||
def show_interfaces(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.interface_display
|
||||
self.update_active_sub_display()
|
||||
self.sub_displays.interface_display.start()
|
||||
|
||||
def show_log(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.log_display
|
||||
self.sub_displays.log_display.show()
|
||||
self.update_active_sub_display()
|
||||
|
||||
def show_guide(self, user_data):
|
||||
self.sub_displays.active_display = self.sub_displays.guide_display
|
||||
self.update_active_sub_display()
|
||||
|
||||
def update_active_sub_display(self):
|
||||
self.frame.contents["body"] = (self.sub_displays.active().widget, None)
|
||||
self.update_active_shortcuts()
|
||||
if self.sub_displays.active_display != self.sub_displays.log_display:
|
||||
self.sub_displays.log_display.kill()
|
||||
|
||||
def update_active_shortcuts(self):
|
||||
self.frame.contents["footer"] = (self.sub_displays.active().shortcuts().widget, None)
|
||||
|
||||
def request_redraw(self, extra_delay=0.0):
|
||||
self.app.ui.loop.set_alarm_in(0.25+extra_delay, self.redraw_now)
|
||||
|
||||
def redraw_now(self, sender=None, data=None):
|
||||
self.app.ui.loop.screen.clear()
|
||||
#self.app.ui.loop.draw_screen()
|
||||
|
||||
def start(self):
|
||||
self.menu_display.start()
|
||||
|
||||
def quit(self, sender=None):
|
||||
logterm_pid = None
|
||||
if True or RNS.vendor.platformutils.is_android():
|
||||
if self.sub_displays.log_display != None and self.sub_displays.log_display.log_term != None:
|
||||
if self.sub_displays.log_display.log_term.log_term != None:
|
||||
logterm_pid = self.sub_displays.log_display.log_term.log_term.pid
|
||||
if logterm_pid != None:
|
||||
import os, signal
|
||||
os.kill(logterm_pid, signal.SIGKILL)
|
||||
|
||||
raise urwid.ExitMainLoop
|
||||
|
||||
|
||||
class MenuColumns(urwid.Columns):
|
||||
def keypress(self, size, key):
|
||||
if key == "tab" or key == "down":
|
||||
self.handler.frame.focus_position = "body"
|
||||
|
||||
return super(MenuColumns, self).keypress(size, key)
|
||||
|
||||
class MenuDisplay():
|
||||
UPDATE_INTERVAL = 2
|
||||
|
||||
def __init__(self, app, handler):
|
||||
self.app = app
|
||||
self.update_interval = MenuDisplay.UPDATE_INTERVAL
|
||||
self.g = self.app.ui.glyphs
|
||||
|
||||
self.menu_indicator = urwid.Text("")
|
||||
|
||||
menu_text = (urwid.PACK, self.menu_indicator)
|
||||
button_network = (11, MenuButton("Network", on_press=handler.show_network))
|
||||
button_conversations = (17, MenuButton("Conversations", on_press=handler.show_conversations))
|
||||
button_channels = (12, MenuButton("Channels", on_press=handler.show_channels))
|
||||
button_directory = (13, MenuButton("Directory", on_press=handler.show_directory))
|
||||
button_map = (7, MenuButton("Map", on_press=handler.show_map))
|
||||
button_log = (7, MenuButton("Log", on_press=handler.show_log))
|
||||
button_config = (10, MenuButton("Config", on_press=handler.show_config))
|
||||
button_interfaces = (14, MenuButton("Interfaces", on_press=handler.show_interfaces))
|
||||
button_guide = (9, MenuButton("Guide", on_press=handler.show_guide))
|
||||
button_quit = (8, MenuButton("Quit", on_press=handler.quit))
|
||||
|
||||
# buttons = [menu_text, button_conversations, button_node, button_directory, button_map]
|
||||
if self.app.config["textui"]["hide_guide"]:
|
||||
buttons = [menu_text, button_conversations, button_network, button_channels, button_log, button_interfaces, button_config, button_quit]
|
||||
else:
|
||||
buttons = [menu_text, button_conversations, button_network, button_channels, button_log, button_interfaces, button_config, button_guide, button_quit]
|
||||
|
||||
columns = MenuColumns(buttons, dividechars=1)
|
||||
columns.handler = handler
|
||||
|
||||
self.update_display()
|
||||
|
||||
self.widget = urwid.AttrMap(columns, "menubar")
|
||||
|
||||
def start(self):
|
||||
self.update_display_job()
|
||||
|
||||
def update_display_job(self, event = None, sender = None):
|
||||
self.update_display()
|
||||
self.app.ui.loop.set_alarm_in(self.update_interval, self.update_display_job)
|
||||
|
||||
def update_display(self):
|
||||
if self.app.has_unread_conversations():
|
||||
self.indicate_unread()
|
||||
else:
|
||||
self.indicate_normal()
|
||||
|
||||
def indicate_normal(self):
|
||||
self.menu_indicator.set_text(self.g["decoration_menu"])
|
||||
|
||||
def indicate_unread(self):
|
||||
self.menu_indicator.set_text(self.g["unread_menu"])
|
||||
@@ -0,0 +1,21 @@
|
||||
class MapDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("Map Display Shortcuts"), "shortcutbar")
|
||||
|
||||
class MapDisplay():
|
||||
def __init__(self, app):
|
||||
import urwid
|
||||
self.app = app
|
||||
|
||||
pile = urwid.Pile([
|
||||
urwid.Text(("body_text", "Map Display \U0001F332")),
|
||||
])
|
||||
|
||||
self.shortcuts_display = MapDisplayShortcuts(self.app)
|
||||
self.widget = urwid.Filler(pile, urwid.TOP)
|
||||
|
||||
def shortcuts(self):
|
||||
return self.shortcuts_display
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
import urwid
|
||||
|
||||
|
||||
_KILL_KEYS = frozenset(("ctrl u", "ctrl k", "ctrl w", "ctrl l"))
|
||||
|
||||
|
||||
class _KillRing:
|
||||
"""Module-global kill buffer shared across all ReadlineMixin widgets.
|
||||
|
||||
Mirrors GNU readline: consecutive kills accumulate into the same entry
|
||||
(forward kills append, backward kills prepend); any non-kill keypress
|
||||
breaks the chain so the next kill replaces the buffer.
|
||||
"""
|
||||
text = ""
|
||||
last_was_kill = False
|
||||
|
||||
@classmethod
|
||||
def reset_chain(cls):
|
||||
cls.last_was_kill = False
|
||||
|
||||
@classmethod
|
||||
def kill(cls, killed, direction):
|
||||
if not killed:
|
||||
return
|
||||
if cls.last_was_kill:
|
||||
cls.text = cls.text + killed if direction == "forward" else killed + cls.text
|
||||
else:
|
||||
cls.text = killed
|
||||
cls.last_was_kill = True
|
||||
|
||||
|
||||
class ReadlineMixin:
|
||||
"""Mixin adding readline-style editing keys to an urwid.Edit-derived widget.
|
||||
|
||||
Bindings (GNU readline defaults, plus ctrl-l as kill-whole-buffer):
|
||||
ctrl-a beginning-of-line
|
||||
ctrl-e end-of-line
|
||||
ctrl-u unix-line-discard (kill from cursor to beginning of line)
|
||||
ctrl-k kill-line (kill from cursor to end of line)
|
||||
ctrl-w unix-word-rubout (kill previous whitespace-delimited word)
|
||||
ctrl-l kill-whole-buffer (kill the entire edit buffer)
|
||||
ctrl-y yank (insert most-recently-killed text)
|
||||
ctrl-left backward-word (alphanumeric boundary)
|
||||
ctrl-right forward-word (alphanumeric boundary)
|
||||
|
||||
"Line" is the current logical line within the edit buffer, delimited by
|
||||
newlines -- so on multiline Edits these act on the line under the cursor,
|
||||
not the entire buffer.
|
||||
|
||||
The kill buffer is shared across all widgets using this mixin, so text
|
||||
killed in one Edit can be yanked into another.
|
||||
"""
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == "ctrl a": self._rl_beg_of_line()
|
||||
elif key == "ctrl e": self._rl_end_of_line()
|
||||
elif key == "ctrl u": self._rl_kill_to_beg()
|
||||
elif key == "ctrl k": self._rl_kill_to_end()
|
||||
elif key == "ctrl w": self._rl_kill_word_back()
|
||||
elif key == "ctrl l": self._rl_kill_whole_buffer()
|
||||
elif key == "ctrl y": self._rl_yank()
|
||||
elif key == "ctrl left": self._rl_backward_word()
|
||||
elif key == "ctrl right": self._rl_forward_word()
|
||||
else:
|
||||
result = super().keypress(size, key)
|
||||
if key not in _KILL_KEYS:
|
||||
_KillRing.reset_chain()
|
||||
return result
|
||||
if key not in _KILL_KEYS:
|
||||
_KillRing.reset_chain()
|
||||
return None
|
||||
|
||||
def _rl_line_bounds(self):
|
||||
text, pos = self.edit_text, self.edit_pos
|
||||
bol = text.rfind("\n", 0, pos)
|
||||
bol = 0 if bol == -1 else bol + 1
|
||||
eol = text.find("\n", pos)
|
||||
eol = len(text) if eol == -1 else eol
|
||||
return bol, eol
|
||||
|
||||
def _rl_delete(self, start, end, kill_direction=None):
|
||||
if start == end:
|
||||
return
|
||||
text = self.edit_text
|
||||
if kill_direction is not None:
|
||||
_KillRing.kill(text[start:end], kill_direction)
|
||||
self.set_edit_text(text[:start] + text[end:])
|
||||
self.set_edit_pos(start)
|
||||
|
||||
@staticmethod
|
||||
def _rl_is_word_char(ch):
|
||||
return ch.isalnum() or ch == "_"
|
||||
|
||||
def _rl_beg_of_line(self):
|
||||
bol, _ = self._rl_line_bounds()
|
||||
self.set_edit_pos(bol)
|
||||
|
||||
def _rl_end_of_line(self):
|
||||
_, eol = self._rl_line_bounds()
|
||||
self.set_edit_pos(eol)
|
||||
|
||||
def _rl_kill_to_beg(self):
|
||||
bol, _ = self._rl_line_bounds()
|
||||
self._rl_delete(bol, self.edit_pos, kill_direction="backward")
|
||||
|
||||
def _rl_kill_to_end(self):
|
||||
_, eol = self._rl_line_bounds()
|
||||
self._rl_delete(self.edit_pos, eol, kill_direction="forward")
|
||||
|
||||
def _rl_kill_word_back(self):
|
||||
text, pos = self.edit_text, self.edit_pos
|
||||
p = pos
|
||||
while p > 0 and text[p - 1].isspace():
|
||||
p -= 1
|
||||
while p > 0 and not text[p - 1].isspace():
|
||||
p -= 1
|
||||
self._rl_delete(p, pos, kill_direction="backward")
|
||||
|
||||
def _rl_kill_whole_buffer(self):
|
||||
self._rl_delete(0, len(self.edit_text), kill_direction="forward")
|
||||
|
||||
def _rl_yank(self):
|
||||
if not _KillRing.text:
|
||||
return
|
||||
pos = self.edit_pos
|
||||
text = self.edit_text
|
||||
self.set_edit_text(text[:pos] + _KillRing.text + text[pos:])
|
||||
self.set_edit_pos(pos + len(_KillRing.text))
|
||||
|
||||
def _rl_backward_word(self):
|
||||
text, pos = self.edit_text, self.edit_pos
|
||||
while pos > 0 and not self._rl_is_word_char(text[pos - 1]):
|
||||
pos -= 1
|
||||
while pos > 0 and self._rl_is_word_char(text[pos - 1]):
|
||||
pos -= 1
|
||||
self.set_edit_pos(pos)
|
||||
|
||||
def _rl_forward_word(self):
|
||||
text, pos = self.edit_text, self.edit_pos
|
||||
n = len(text)
|
||||
while pos < n and not self._rl_is_word_char(text[pos]):
|
||||
pos += 1
|
||||
while pos < n and self._rl_is_word_char(text[pos]):
|
||||
pos += 1
|
||||
self.set_edit_pos(pos)
|
||||
|
||||
|
||||
class ReadlineEdit(ReadlineMixin, urwid.Edit):
|
||||
"""Drop-in urwid.Edit replacement with readline-style editing keys."""
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
Reference in New Issue
Block a user