suplemon-editor 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- suplemon/__init__.py +0 -0
- suplemon/__main__.py +3 -0
- suplemon/cli.py +65 -0
- suplemon/config/defaults.json +153 -0
- suplemon/config/keymap.json +63 -0
- suplemon/config.py +324 -0
- suplemon/cursor.py +135 -0
- suplemon/editor.py +568 -0
- suplemon/file.py +193 -0
- suplemon/help.py +327 -0
- suplemon/helpers.py +104 -0
- suplemon/hex2xterm.py +23 -0
- suplemon/key_mappings.py +239 -0
- suplemon/lexer.py +88 -0
- suplemon/line.py +51 -0
- suplemon/linelight/__init__.py +0 -0
- suplemon/linelight/color_map.py +9 -0
- suplemon/linelight/css.py +21 -0
- suplemon/linelight/diff.py +17 -0
- suplemon/linelight/html.py +19 -0
- suplemon/linelight/js.py +21 -0
- suplemon/linelight/json.py +15 -0
- suplemon/linelight/lua.py +10 -0
- suplemon/linelight/md.py +19 -0
- suplemon/linelight/php.py +27 -0
- suplemon/linelight/py.py +27 -0
- suplemon/logger.py +90 -0
- suplemon/main.py +771 -0
- suplemon/module_loader.py +98 -0
- suplemon/modules/application_state.py +84 -0
- suplemon/modules/autocomplete.py +114 -0
- suplemon/modules/autodocstring.py +142 -0
- suplemon/modules/battery.py +118 -0
- suplemon/modules/bulk_delete.py +75 -0
- suplemon/modules/clock.py +22 -0
- suplemon/modules/comment.py +59 -0
- suplemon/modules/config.py +20 -0
- suplemon/modules/date.py +22 -0
- suplemon/modules/diff.py +42 -0
- suplemon/modules/eval.py +43 -0
- suplemon/modules/hostname.py +34 -0
- suplemon/modules/input_test.py +23 -0
- suplemon/modules/keymap.py +20 -0
- suplemon/modules/linter.py +274 -0
- suplemon/modules/lower.py +21 -0
- suplemon/modules/lstrip.py +20 -0
- suplemon/modules/paste.py +49 -0
- suplemon/modules/reload.py +16 -0
- suplemon/modules/replace_all.py +23 -0
- suplemon/modules/reverse.py +22 -0
- suplemon/modules/rstrip.py +19 -0
- suplemon/modules/save.py +16 -0
- suplemon/modules/save_all.py +19 -0
- suplemon/modules/sort_lines.py +41 -0
- suplemon/modules/strip.py +19 -0
- suplemon/modules/system_clipboard.py +121 -0
- suplemon/modules/tabstospaces.py +18 -0
- suplemon/modules/toggle_whitespace.py +18 -0
- suplemon/modules/upper.py +21 -0
- suplemon/prompt.py +319 -0
- suplemon/suplemon_module.py +210 -0
- suplemon/themes/8colors.tmTheme +179 -0
- suplemon/themes/monokai.tmTheme +284 -0
- suplemon/themes.py +201 -0
- suplemon/ui.py +656 -0
- suplemon/viewer.py +1004 -0
- suplemon_editor-0.3.0.dist-info/METADATA +515 -0
- suplemon_editor-0.3.0.dist-info/RECORD +72 -0
- suplemon_editor-0.3.0.dist-info/WHEEL +5 -0
- suplemon_editor-0.3.0.dist-info/entry_points.txt +3 -0
- suplemon_editor-0.3.0.dist-info/licenses/LICENSE +21 -0
- suplemon_editor-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
"""
|
|
3
|
+
Addon module loader.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import importlib.util
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModuleLoader:
|
|
11
|
+
def __init__(self, app=None):
|
|
12
|
+
self.app = app
|
|
13
|
+
self.logger = logging.getLogger(__name__)
|
|
14
|
+
# The app root directory
|
|
15
|
+
self.curr_path = os.path.dirname(os.path.realpath(__file__))
|
|
16
|
+
# The modules subdirectory
|
|
17
|
+
self.module_path = os.path.join(self.curr_path, "modules" + os.sep)
|
|
18
|
+
# Module instances
|
|
19
|
+
self.modules = {}
|
|
20
|
+
|
|
21
|
+
def load(self):
|
|
22
|
+
"""Find and load available modules."""
|
|
23
|
+
self.logger.debug("Loading modules...")
|
|
24
|
+
names = self.get_module_names()
|
|
25
|
+
for name in names:
|
|
26
|
+
module = self.load_single(name)
|
|
27
|
+
if module:
|
|
28
|
+
# Load and store the module instance
|
|
29
|
+
inst = self.load_instance(module)
|
|
30
|
+
if inst:
|
|
31
|
+
self.modules[module[0]] = inst
|
|
32
|
+
|
|
33
|
+
def get_module_names(self):
|
|
34
|
+
"""Get names of loadable modules."""
|
|
35
|
+
names = []
|
|
36
|
+
dirlist = os.listdir(self.module_path)
|
|
37
|
+
for item in dirlist:
|
|
38
|
+
# Skip 'hidden' dot files and files beginning with and underscore
|
|
39
|
+
if item.startswith((".", "_")):
|
|
40
|
+
continue
|
|
41
|
+
parts = item.split(".")
|
|
42
|
+
if len(parts) < 2:
|
|
43
|
+
# Can't find file extension
|
|
44
|
+
continue
|
|
45
|
+
name = parts[0]
|
|
46
|
+
ext = parts[-1]
|
|
47
|
+
# only load .py modules
|
|
48
|
+
if ext != "py":
|
|
49
|
+
continue
|
|
50
|
+
names.append(name)
|
|
51
|
+
return names
|
|
52
|
+
|
|
53
|
+
def load_instance(self, module):
|
|
54
|
+
"""Initialize a module."""
|
|
55
|
+
try:
|
|
56
|
+
inst = module[1]["class"](self.app, module[0], module[1]) # Store the module instance
|
|
57
|
+
return inst
|
|
58
|
+
except Exception:
|
|
59
|
+
self.logger.error("Initializing module failed: {0}".format(module[0]), exc_info=True)
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def load_single(self, name):
|
|
63
|
+
"""Load single module file."""
|
|
64
|
+
path = os.path.join(self.module_path, name+".py")
|
|
65
|
+
try:
|
|
66
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
67
|
+
mod = importlib.util.module_from_spec(spec)
|
|
68
|
+
spec.loader.exec_module(mod)
|
|
69
|
+
except Exception:
|
|
70
|
+
self.logger.error("Failed loading module: {0}".format(name), exc_info=True)
|
|
71
|
+
return False
|
|
72
|
+
if "module" not in dir(mod):
|
|
73
|
+
return False
|
|
74
|
+
if "status" not in mod.module.keys():
|
|
75
|
+
mod.module["status"] = False
|
|
76
|
+
return name, mod.module
|
|
77
|
+
|
|
78
|
+
def extract_docs(self):
|
|
79
|
+
"""Get names and docs of runnable modules and print as markdown."""
|
|
80
|
+
names = sorted(self.get_module_names())
|
|
81
|
+
for name in names:
|
|
82
|
+
name, module = self.load_single(name)
|
|
83
|
+
# Skip modules that can't be run expicitly
|
|
84
|
+
if module["class"].run.__module__ == "suplemon.suplemon_module":
|
|
85
|
+
continue
|
|
86
|
+
# Skip undocumented modules
|
|
87
|
+
if not module["class"].__doc__:
|
|
88
|
+
continue
|
|
89
|
+
docstring = module["class"].__doc__
|
|
90
|
+
docstring = "\n " + docstring.strip()
|
|
91
|
+
|
|
92
|
+
doc = " * {0}\n{1}\n".format(name, docstring)
|
|
93
|
+
print(doc)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
ml = ModuleLoader()
|
|
98
|
+
ml.extract_docs()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
|
|
6
|
+
from suplemon.suplemon_module import Module
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApplicationState(Module):
|
|
10
|
+
"""
|
|
11
|
+
Stores the state of open files when exiting the editor and restores when files are reopened.
|
|
12
|
+
|
|
13
|
+
Cursor positions and scroll position are stored and restored.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def init(self):
|
|
17
|
+
self.init_logging(__name__)
|
|
18
|
+
self.bind_event_after("app_loaded", self.on_load)
|
|
19
|
+
self.bind_event_before("app_exit", self.on_exit)
|
|
20
|
+
|
|
21
|
+
def on_load(self, event):
|
|
22
|
+
"""Runs when suplemon is fully loaded."""
|
|
23
|
+
self.restore_states()
|
|
24
|
+
|
|
25
|
+
def on_exit(self, event):
|
|
26
|
+
"""Runs before suplemon is exits."""
|
|
27
|
+
self.store_states()
|
|
28
|
+
|
|
29
|
+
def get_file_states(self):
|
|
30
|
+
"""Get the state of currently opened files. Returns a dict with the file path as key and file state as value."""
|
|
31
|
+
states = {}
|
|
32
|
+
for file in self.app.get_files():
|
|
33
|
+
states[file.get_path()] = self.get_file_state(file)
|
|
34
|
+
return states
|
|
35
|
+
|
|
36
|
+
def get_file_state(self, file):
|
|
37
|
+
"""Get the state of a single file."""
|
|
38
|
+
editor = file.get_editor()
|
|
39
|
+
state = {
|
|
40
|
+
"cursors": [cursor.tuple() for cursor in editor.cursors],
|
|
41
|
+
"scroll_pos": editor.scroll_pos,
|
|
42
|
+
"hash": self.get_hash(editor),
|
|
43
|
+
}
|
|
44
|
+
return state
|
|
45
|
+
|
|
46
|
+
def get_hash(self, editor):
|
|
47
|
+
# We don't need cryptographic security so we just use md5
|
|
48
|
+
h = hashlib.md5()
|
|
49
|
+
for line in editor.lines:
|
|
50
|
+
h.update(line.get_data().encode("utf-8"))
|
|
51
|
+
return h.hexdigest()
|
|
52
|
+
|
|
53
|
+
def set_file_state(self, file, state):
|
|
54
|
+
"""Set the state of a file."""
|
|
55
|
+
cursor = file.editor.get_cursor()
|
|
56
|
+
# Don't set the cursor pos if it's not the default 0,0
|
|
57
|
+
if cursor.x or cursor.y:
|
|
58
|
+
return
|
|
59
|
+
file.editor.set_cursors(state["cursors"])
|
|
60
|
+
file.editor.scroll_pos = state["scroll_pos"]
|
|
61
|
+
|
|
62
|
+
def store_states(self):
|
|
63
|
+
"""Store the states of opened files."""
|
|
64
|
+
states = self.get_file_states()
|
|
65
|
+
for path in states.keys():
|
|
66
|
+
self.storage[path] = states[path]
|
|
67
|
+
self.storage.store()
|
|
68
|
+
|
|
69
|
+
def restore_states(self):
|
|
70
|
+
"""Restore the states of files that are currently open."""
|
|
71
|
+
for file in self.app.get_files():
|
|
72
|
+
path = file.get_path()
|
|
73
|
+
if path in self.storage.get_data().keys():
|
|
74
|
+
state = self.storage[path]
|
|
75
|
+
if "hash" not in state:
|
|
76
|
+
self.set_file_state(file, state)
|
|
77
|
+
elif state["hash"] == self.get_hash(file.get_editor()):
|
|
78
|
+
self.set_file_state(file, state)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
module = {
|
|
82
|
+
"class": ApplicationState,
|
|
83
|
+
"name": "application_state",
|
|
84
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from suplemon import helpers
|
|
6
|
+
from suplemon.suplemon_module import Module
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AutoComplete(Module):
|
|
10
|
+
"""
|
|
11
|
+
A simple autocompletion module.
|
|
12
|
+
|
|
13
|
+
This adds autocomplete support for the tab key. It uses a word
|
|
14
|
+
list scanned from all open files for completions. By default it suggests
|
|
15
|
+
the shortest possible match. If there are no matches, the tab action is
|
|
16
|
+
run normally.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def init(self):
|
|
20
|
+
self.word_list = []
|
|
21
|
+
self.bind_event("tab", self.auto_complete)
|
|
22
|
+
self.bind_event_after("app_loaded", self.build_word_list)
|
|
23
|
+
self.bind_event_after("save_file", self.build_word_list)
|
|
24
|
+
self.bind_event_after("save_file_as", self.build_word_list)
|
|
25
|
+
|
|
26
|
+
def get_separators(self):
|
|
27
|
+
"""Return list of word separators obtained from app config.
|
|
28
|
+
|
|
29
|
+
:return: String with all separators.
|
|
30
|
+
:rtype: str
|
|
31
|
+
"""
|
|
32
|
+
separators = self.app.config["editor"]["punctuation"]
|
|
33
|
+
# Support words with underscores
|
|
34
|
+
separators = separators.replace("_", "")
|
|
35
|
+
return separators
|
|
36
|
+
|
|
37
|
+
def build_word_list(self, *args):
|
|
38
|
+
"""Build the word list based on contents of open files."""
|
|
39
|
+
word_list = []
|
|
40
|
+
# Membership is tracked in a set. Checking "word not in word_list"
|
|
41
|
+
# scans the whole list for every word in every open file, which is
|
|
42
|
+
# what made opening a large file take minutes.
|
|
43
|
+
seen = set()
|
|
44
|
+
for file in self.app.files:
|
|
45
|
+
data = file.get_editor().get_data()
|
|
46
|
+
words = helpers.multisplit(data, self.get_separators())
|
|
47
|
+
for word in words:
|
|
48
|
+
# Discard undesired whitespace
|
|
49
|
+
word = word.strip()
|
|
50
|
+
# Must be longer than 1 and not yet in word_list
|
|
51
|
+
if len(word) > 1 and word not in seen:
|
|
52
|
+
seen.add(word)
|
|
53
|
+
word_list.append(word)
|
|
54
|
+
self.word_list = word_list
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def get_match(self, word):
|
|
58
|
+
"""Find a completable match for word.
|
|
59
|
+
|
|
60
|
+
:param word: The partial word to complete
|
|
61
|
+
:return: The completion to add to the partial word
|
|
62
|
+
:rtype: str
|
|
63
|
+
"""
|
|
64
|
+
if not word:
|
|
65
|
+
return False
|
|
66
|
+
# Build list of suitable matches
|
|
67
|
+
candidates = []
|
|
68
|
+
for candidate in self.word_list:
|
|
69
|
+
if candidate.startswith(word) and len(candidate) > len(word):
|
|
70
|
+
candidates.append(candidate)
|
|
71
|
+
# Find the shortest match
|
|
72
|
+
# TODO: implement cycling through matches
|
|
73
|
+
shortest = ""
|
|
74
|
+
for candidate in candidates:
|
|
75
|
+
if not shortest:
|
|
76
|
+
shortest = candidate
|
|
77
|
+
continue
|
|
78
|
+
if len(candidate) < len(shortest):
|
|
79
|
+
shortest = candidate
|
|
80
|
+
if shortest:
|
|
81
|
+
return shortest[len(word):]
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def run(self, app, editor, args):
|
|
85
|
+
"""Run the autocompletion."""
|
|
86
|
+
self.auto_complete()
|
|
87
|
+
|
|
88
|
+
def auto_complete(self, event):
|
|
89
|
+
"""Attempt to autocomplete at each cursor position.
|
|
90
|
+
|
|
91
|
+
This callback runs before the tab action and tries to autocomplete
|
|
92
|
+
the current word. If a match is found the tab action is inhibited.
|
|
93
|
+
|
|
94
|
+
:param event: The event object.
|
|
95
|
+
:return: True if a match is found.
|
|
96
|
+
"""
|
|
97
|
+
editor = self.app.get_editor()
|
|
98
|
+
pattern = "|".join(map(re.escape, self.get_separators()))
|
|
99
|
+
matched = False
|
|
100
|
+
for cursor in editor.cursors:
|
|
101
|
+
line = editor.lines[cursor.y][:cursor.x]
|
|
102
|
+
words = re.split(pattern, line)
|
|
103
|
+
last_word = words[-1]
|
|
104
|
+
match = self.get_match(last_word)
|
|
105
|
+
if match:
|
|
106
|
+
matched = True
|
|
107
|
+
editor.type_at_cursor(cursor, match)
|
|
108
|
+
return matched
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
module = {
|
|
112
|
+
"class": AutoComplete,
|
|
113
|
+
"name": "autocomplete",
|
|
114
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon import helpers
|
|
4
|
+
from suplemon.suplemon_module import Module
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AutoDocstring(Module):
|
|
8
|
+
"""
|
|
9
|
+
Simple module for adding docstring placeholders.
|
|
10
|
+
|
|
11
|
+
This module is intended to generate docstrings for Python functions.
|
|
12
|
+
It adds placeholders for descriptions, arguments and return data.
|
|
13
|
+
Function arguments are crudely parsed from the function definition
|
|
14
|
+
and return statements are scanned from the function body.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def init(self):
|
|
18
|
+
self.init_logging(__name__)
|
|
19
|
+
self.default_values = {
|
|
20
|
+
"short_desc": "Short description.",
|
|
21
|
+
"long_desc": "Long description.",
|
|
22
|
+
"args": "",
|
|
23
|
+
"returns": "",
|
|
24
|
+
}
|
|
25
|
+
self.docstring_template = "{short_desc}\n" + \
|
|
26
|
+
"\n" + \
|
|
27
|
+
"{long_desc}\n" + \
|
|
28
|
+
"\n" + \
|
|
29
|
+
"{args}" + \
|
|
30
|
+
"{returns}"
|
|
31
|
+
|
|
32
|
+
def run(self, app, editor, args):
|
|
33
|
+
"""Run the autosphinx command."""
|
|
34
|
+
cursor = editor.get_cursor()
|
|
35
|
+
line = editor.get_line(cursor.y)
|
|
36
|
+
line_data = line.get_data()
|
|
37
|
+
if not line_data.strip().startswith("def "):
|
|
38
|
+
app.set_status("Current line isn't a function definition.")
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
func_args = self.get_function_args(line_data)
|
|
42
|
+
func_returns = self.get_function_returns(editor, cursor.y)
|
|
43
|
+
docstring = self.get_docstring(func_args=func_args, func_returns=func_returns)
|
|
44
|
+
|
|
45
|
+
indent = self.get_docstring_indent(line_data)
|
|
46
|
+
indent = indent * self.app.config["editor"]["tab_width"] * " "
|
|
47
|
+
self.insert_docstring(editor, cursor.y+1, indent, docstring)
|
|
48
|
+
|
|
49
|
+
def insert_docstring(self, editor, line_number, indent, docstring):
|
|
50
|
+
"""Insert a docstring at a specific line.
|
|
51
|
+
|
|
52
|
+
Inserts the given docstring into editor at a specific
|
|
53
|
+
line and indentation.
|
|
54
|
+
|
|
55
|
+
:param editor: The editor to insert into
|
|
56
|
+
:param int line_number: The first line number
|
|
57
|
+
:param str indent: How much the docstring
|
|
58
|
+
:param str docstring: The actual docstring
|
|
59
|
+
"""
|
|
60
|
+
docstring = '"""{0}"""'.format(docstring)
|
|
61
|
+
raw_lines = docstring.split("\n")
|
|
62
|
+
lines = []
|
|
63
|
+
for line in raw_lines:
|
|
64
|
+
if not line.strip():
|
|
65
|
+
lines.append("")
|
|
66
|
+
else:
|
|
67
|
+
lines.append(indent+line)
|
|
68
|
+
editor.insert_lines_at(lines, line_number)
|
|
69
|
+
|
|
70
|
+
def get_docstring_indent(self, line_data):
|
|
71
|
+
"""Get indent amount for docstring.
|
|
72
|
+
|
|
73
|
+
Gets the indentation of the function definiton line, and
|
|
74
|
+
adds +1 to account for the function body.
|
|
75
|
+
|
|
76
|
+
:param line_data: The line of the function definition.
|
|
77
|
+
"""
|
|
78
|
+
tab = self.app.config["editor"]["tab_width"]
|
|
79
|
+
wspace = helpers.whitespace(line_data)
|
|
80
|
+
indent = int(wspace/tab)+1
|
|
81
|
+
return indent
|
|
82
|
+
|
|
83
|
+
def get_docstring(self, **values):
|
|
84
|
+
for key in self.default_values.keys():
|
|
85
|
+
if key not in values.keys():
|
|
86
|
+
values[key] = self.default_values[key]
|
|
87
|
+
|
|
88
|
+
args = ""
|
|
89
|
+
for arg in values["func_args"]:
|
|
90
|
+
args += ":param {0}:\n".format(arg)
|
|
91
|
+
values["args"] = args
|
|
92
|
+
|
|
93
|
+
if values["func_returns"]:
|
|
94
|
+
values["returns"] = ":return:\n"
|
|
95
|
+
|
|
96
|
+
doc = self.docstring_template
|
|
97
|
+
doc = doc.format(**values)
|
|
98
|
+
return doc
|
|
99
|
+
|
|
100
|
+
def get_function_name(self, line_data):
|
|
101
|
+
"""Get the name of function defined in line_data.
|
|
102
|
+
|
|
103
|
+
:param str line_data: The line containing the function definition.
|
|
104
|
+
"""
|
|
105
|
+
return helpers.get_string_between("def ", "(", line_data)
|
|
106
|
+
|
|
107
|
+
def get_function_args(self, line_data):
|
|
108
|
+
"""Get list of arguments for function
|
|
109
|
+
|
|
110
|
+
Parses a function definition in line_data and returns argument list.
|
|
111
|
+
|
|
112
|
+
:param str line_data: The line containing the function definition.
|
|
113
|
+
"""
|
|
114
|
+
func_name = self.get_function_name(line_data)
|
|
115
|
+
raw_args = helpers.get_string_between(func_name+"(", "):", line_data)
|
|
116
|
+
parts = raw_args.split(",")
|
|
117
|
+
args = [part.strip() for part in parts]
|
|
118
|
+
if "self" in args:
|
|
119
|
+
args.pop(args.index("self"))
|
|
120
|
+
return args
|
|
121
|
+
|
|
122
|
+
def get_function_returns(self, editor, line_number):
|
|
123
|
+
"""Returns True if function at line_number returns something.
|
|
124
|
+
|
|
125
|
+
:param editor: Editor instance to get lines from.
|
|
126
|
+
:param line_number: Line number of the function definition.
|
|
127
|
+
:return: Boolean indicating wether the function something.
|
|
128
|
+
"""
|
|
129
|
+
for i in range(line_number+1, len(editor.lines)):
|
|
130
|
+
line = editor.get_line(i)
|
|
131
|
+
data = line.get_data().strip()
|
|
132
|
+
if data.startswith("def "):
|
|
133
|
+
break
|
|
134
|
+
if data.startswith("return "):
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
module = {
|
|
140
|
+
"class": AutoDocstring,
|
|
141
|
+
"name": "autodocstring",
|
|
142
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from suplemon import helpers
|
|
8
|
+
from suplemon.suplemon_module import Module
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Battery(Module):
|
|
12
|
+
"""Shows remaining battery capacity in the top status bar if available."""
|
|
13
|
+
|
|
14
|
+
def init(self):
|
|
15
|
+
self.last_value = -1
|
|
16
|
+
self.checked = time.time()
|
|
17
|
+
self.interval = 60 # Seconds to wait until polling again
|
|
18
|
+
|
|
19
|
+
def value(self):
|
|
20
|
+
"""Get the battery charge percent and cache it."""
|
|
21
|
+
if self.last_value == -1:
|
|
22
|
+
state = self.battery_status()
|
|
23
|
+
elif time.time()-self.checked > self.interval:
|
|
24
|
+
state = self.battery_status()
|
|
25
|
+
else:
|
|
26
|
+
return self.last_value
|
|
27
|
+
self.last_value = state
|
|
28
|
+
return state
|
|
29
|
+
|
|
30
|
+
def value_str(self):
|
|
31
|
+
"""Return formatted value string to show in the UI."""
|
|
32
|
+
val = self.value()
|
|
33
|
+
if val:
|
|
34
|
+
if self.app.config["app"]["use_unicode_symbols"]:
|
|
35
|
+
return "\u26A1{0}%".format(str(val))
|
|
36
|
+
else:
|
|
37
|
+
return "BAT {0}%".format(str(val))
|
|
38
|
+
return ""
|
|
39
|
+
|
|
40
|
+
def get_status(self):
|
|
41
|
+
"""Called by app when showing status bar contents."""
|
|
42
|
+
return self.value_str()
|
|
43
|
+
|
|
44
|
+
def battery_status(self):
|
|
45
|
+
"""Attempts to get the battery charge percent."""
|
|
46
|
+
value = None
|
|
47
|
+
methods = [
|
|
48
|
+
self.battery_status_read,
|
|
49
|
+
self.battery_status_acpi,
|
|
50
|
+
self.battery_status_upower
|
|
51
|
+
]
|
|
52
|
+
for m in methods:
|
|
53
|
+
value = m()
|
|
54
|
+
if value is not None:
|
|
55
|
+
break
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
def battery_status_read(self):
|
|
59
|
+
"""Get the battery status via proc/acpi."""
|
|
60
|
+
try:
|
|
61
|
+
path_info = self.readf("/proc/acpi/battery/BAT0/info")
|
|
62
|
+
path_state = self.readf("/proc/acpi/battery/BAT0/state")
|
|
63
|
+
except OSError:
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
max_cap = float(helpers.get_string_between("last full capacity:", "mWh", path_info))
|
|
67
|
+
cur_cap = float(helpers.get_string_between("remaining capacity:", "mWh", path_state))
|
|
68
|
+
return int(cur_cap / max_cap * 100)
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def battery_status_acpi(self):
|
|
73
|
+
"""Get the battery status via acpi."""
|
|
74
|
+
try:
|
|
75
|
+
fnull = open(os.devnull, "w")
|
|
76
|
+
raw_str = subprocess.check_output(["acpi"], stderr=fnull)
|
|
77
|
+
fnull.close()
|
|
78
|
+
except (OSError, subprocess.CalledProcessError):
|
|
79
|
+
return None
|
|
80
|
+
raw_str = raw_str.decode("utf-8")
|
|
81
|
+
part = helpers.get_string_between(",", "%", raw_str)
|
|
82
|
+
if part:
|
|
83
|
+
try:
|
|
84
|
+
return int(part)
|
|
85
|
+
except ValueError:
|
|
86
|
+
return None
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def battery_status_upower(self):
|
|
90
|
+
"""Get the battery status via upower."""
|
|
91
|
+
path = "/org/freedesktop/UPower/devices/battery_BAT0"
|
|
92
|
+
try:
|
|
93
|
+
raw_str = subprocess.check_output(["upower", "-i", path])
|
|
94
|
+
except (OSError, subprocess.CalledProcessError):
|
|
95
|
+
return None
|
|
96
|
+
raw_str = raw_str.decode("utf-8")
|
|
97
|
+
raw_str = raw_str.splitlines()[0]
|
|
98
|
+
part = helpers.get_string_between("percentage:", "%", raw_str)
|
|
99
|
+
if part:
|
|
100
|
+
try:
|
|
101
|
+
return int(part)
|
|
102
|
+
except ValueError:
|
|
103
|
+
return None
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def readf(self, path):
|
|
107
|
+
"""Read and return file contents at path."""
|
|
108
|
+
f = open(path)
|
|
109
|
+
data = f.read()
|
|
110
|
+
f.close()
|
|
111
|
+
return data
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
module = {
|
|
115
|
+
"class": Battery,
|
|
116
|
+
"name": "battery",
|
|
117
|
+
"status": "top",
|
|
118
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BulkDelete(Module):
|
|
7
|
+
"""
|
|
8
|
+
Bulk delete lines and characters.
|
|
9
|
+
Asks what direction to delete in by default.
|
|
10
|
+
|
|
11
|
+
Add 'up' to delete lines above highest cursor.
|
|
12
|
+
Add 'down' to delete lines below lowest cursor.
|
|
13
|
+
Add 'left' to delete characters to the left of all cursors.
|
|
14
|
+
Add 'right' to delete characters to the right of all cursors.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def init(self):
|
|
18
|
+
self.directions = ["up", "down", "left", "right"]
|
|
19
|
+
|
|
20
|
+
def handler(self, prompt, event):
|
|
21
|
+
# Get arrow keys from prompt
|
|
22
|
+
if event.key_name in self.directions:
|
|
23
|
+
prompt.set_data(event.key_name)
|
|
24
|
+
prompt.on_ready()
|
|
25
|
+
return True # Disable normal key handling
|
|
26
|
+
|
|
27
|
+
def run(self, app, editor, args):
|
|
28
|
+
direction = args.lower()
|
|
29
|
+
if not direction:
|
|
30
|
+
direction = app.ui.query_filtered("Press arrow key in direction to delete:", handler=self.handler)
|
|
31
|
+
|
|
32
|
+
if direction not in self.directions:
|
|
33
|
+
app.set_status("Invalid direction.")
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
# Delete entire lines
|
|
37
|
+
if direction == "up":
|
|
38
|
+
pos = editor.get_first_cursor()
|
|
39
|
+
length = len(editor.lines)
|
|
40
|
+
editor.lines = editor.lines[pos.y:]
|
|
41
|
+
delta = length - len(editor.lines)
|
|
42
|
+
# If lines were removed, move the cursors up the same amount
|
|
43
|
+
if delta:
|
|
44
|
+
editor.move_cursors((0, -delta))
|
|
45
|
+
|
|
46
|
+
elif direction == "down":
|
|
47
|
+
pos = editor.get_last_cursor()
|
|
48
|
+
editor.lines = editor.lines[:pos.y+1]
|
|
49
|
+
|
|
50
|
+
# Delete from start or end of lines
|
|
51
|
+
else:
|
|
52
|
+
# Select min/max function based on direction
|
|
53
|
+
func = min if direction == "left" else max
|
|
54
|
+
# Get all lines with cursors
|
|
55
|
+
line_indices = editor.get_lines_with_cursors()
|
|
56
|
+
for line_no in line_indices:
|
|
57
|
+
# Get all cursors for the line
|
|
58
|
+
line_cursors = editor.get_cursors_on_line(line_no)
|
|
59
|
+
# Get the leftmost of rightmost x coordinate
|
|
60
|
+
x = func(line_cursors, key=lambda c: c.x).x
|
|
61
|
+
|
|
62
|
+
# Delete correct part of the line
|
|
63
|
+
line = editor.lines[line_no]
|
|
64
|
+
if direction == "left":
|
|
65
|
+
line.data = line.data[x:]
|
|
66
|
+
# Also move cursors appropriately when deleting left side
|
|
67
|
+
[c.move_left(x) for c in line_cursors]
|
|
68
|
+
else:
|
|
69
|
+
line.data = line.data[:x]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
module = {
|
|
73
|
+
"class": BulkDelete,
|
|
74
|
+
"name": "bulk_delete",
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from suplemon.suplemon_module import Module
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Clock(Module):
|
|
9
|
+
"""Shows a clock in the top status bar."""
|
|
10
|
+
|
|
11
|
+
def get_status(self):
|
|
12
|
+
s = time.strftime("%H:%M")
|
|
13
|
+
if self.app.config["app"]["use_unicode_symbols"]:
|
|
14
|
+
return "\u231A" + s
|
|
15
|
+
return s
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module = {
|
|
19
|
+
"class": Clock,
|
|
20
|
+
"name": "clock",
|
|
21
|
+
"status": "top",
|
|
22
|
+
}
|