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.
Files changed (72) hide show
  1. suplemon/__init__.py +0 -0
  2. suplemon/__main__.py +3 -0
  3. suplemon/cli.py +65 -0
  4. suplemon/config/defaults.json +153 -0
  5. suplemon/config/keymap.json +63 -0
  6. suplemon/config.py +324 -0
  7. suplemon/cursor.py +135 -0
  8. suplemon/editor.py +568 -0
  9. suplemon/file.py +193 -0
  10. suplemon/help.py +327 -0
  11. suplemon/helpers.py +104 -0
  12. suplemon/hex2xterm.py +23 -0
  13. suplemon/key_mappings.py +239 -0
  14. suplemon/lexer.py +88 -0
  15. suplemon/line.py +51 -0
  16. suplemon/linelight/__init__.py +0 -0
  17. suplemon/linelight/color_map.py +9 -0
  18. suplemon/linelight/css.py +21 -0
  19. suplemon/linelight/diff.py +17 -0
  20. suplemon/linelight/html.py +19 -0
  21. suplemon/linelight/js.py +21 -0
  22. suplemon/linelight/json.py +15 -0
  23. suplemon/linelight/lua.py +10 -0
  24. suplemon/linelight/md.py +19 -0
  25. suplemon/linelight/php.py +27 -0
  26. suplemon/linelight/py.py +27 -0
  27. suplemon/logger.py +90 -0
  28. suplemon/main.py +771 -0
  29. suplemon/module_loader.py +98 -0
  30. suplemon/modules/application_state.py +84 -0
  31. suplemon/modules/autocomplete.py +114 -0
  32. suplemon/modules/autodocstring.py +142 -0
  33. suplemon/modules/battery.py +118 -0
  34. suplemon/modules/bulk_delete.py +75 -0
  35. suplemon/modules/clock.py +22 -0
  36. suplemon/modules/comment.py +59 -0
  37. suplemon/modules/config.py +20 -0
  38. suplemon/modules/date.py +22 -0
  39. suplemon/modules/diff.py +42 -0
  40. suplemon/modules/eval.py +43 -0
  41. suplemon/modules/hostname.py +34 -0
  42. suplemon/modules/input_test.py +23 -0
  43. suplemon/modules/keymap.py +20 -0
  44. suplemon/modules/linter.py +274 -0
  45. suplemon/modules/lower.py +21 -0
  46. suplemon/modules/lstrip.py +20 -0
  47. suplemon/modules/paste.py +49 -0
  48. suplemon/modules/reload.py +16 -0
  49. suplemon/modules/replace_all.py +23 -0
  50. suplemon/modules/reverse.py +22 -0
  51. suplemon/modules/rstrip.py +19 -0
  52. suplemon/modules/save.py +16 -0
  53. suplemon/modules/save_all.py +19 -0
  54. suplemon/modules/sort_lines.py +41 -0
  55. suplemon/modules/strip.py +19 -0
  56. suplemon/modules/system_clipboard.py +121 -0
  57. suplemon/modules/tabstospaces.py +18 -0
  58. suplemon/modules/toggle_whitespace.py +18 -0
  59. suplemon/modules/upper.py +21 -0
  60. suplemon/prompt.py +319 -0
  61. suplemon/suplemon_module.py +210 -0
  62. suplemon/themes/8colors.tmTheme +179 -0
  63. suplemon/themes/monokai.tmTheme +284 -0
  64. suplemon/themes.py +201 -0
  65. suplemon/ui.py +656 -0
  66. suplemon/viewer.py +1004 -0
  67. suplemon_editor-0.3.0.dist-info/METADATA +515 -0
  68. suplemon_editor-0.3.0.dist-info/RECORD +72 -0
  69. suplemon_editor-0.3.0.dist-info/WHEEL +5 -0
  70. suplemon_editor-0.3.0.dist-info/entry_points.txt +3 -0
  71. suplemon_editor-0.3.0.dist-info/licenses/LICENSE +21 -0
  72. 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
+ }