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,22 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class Reverse(Module):
7
+ """Reverse text on current line(s)."""
8
+
9
+ def run(self, app, editor, args):
10
+ line_nums = []
11
+ for cursor in editor.cursors:
12
+ if cursor.y not in line_nums:
13
+ line_nums.append(cursor.y)
14
+ # Reverse string
15
+ data = editor.lines[cursor.y].data[::-1]
16
+ editor.lines[cursor.y].set_data(data)
17
+
18
+
19
+ module = {
20
+ "class": Reverse,
21
+ "name": "upper",
22
+ }
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class RStrip(Module):
7
+ """Trim whitespace from the end of lines."""
8
+
9
+ def run(self, app, editor, args):
10
+ line_nums = editor.get_lines_with_cursors()
11
+ for n in line_nums:
12
+ line = editor.lines[n]
13
+ line.set_data(line.data.rstrip())
14
+
15
+
16
+ module = {
17
+ "class": RStrip,
18
+ "name": "rstrip",
19
+ }
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class Save(Module):
7
+ """Save the current file."""
8
+
9
+ def run(self, app, editor, args):
10
+ return app.save_file()
11
+
12
+
13
+ module = {
14
+ "class": Save,
15
+ "name": "save",
16
+ }
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class SaveAll(Module):
7
+ """Save all currently open files. Asks for confirmation."""
8
+
9
+ def run(self, app, editor, args):
10
+ if not self.app.ui.query_bool("Save all files?", False):
11
+ return False
12
+ for file in app.get_files():
13
+ file.save()
14
+
15
+
16
+ module = {
17
+ "class": SaveAll,
18
+ "name": "save_all",
19
+ }
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class SortLines(Module):
7
+ """
8
+ Sort current lines.
9
+
10
+ Sorts alphabetically by default.
11
+ Add 'length' to sort by length.
12
+ Add 'reverse' to reverse the sorting.
13
+ """
14
+
15
+ def sort_normal(self, line):
16
+ return line.data
17
+
18
+ def sort_length(self, line):
19
+ return len(line.data)
20
+
21
+ def run(self, app, editor, args):
22
+ args = args.lower()
23
+
24
+ sorter = self.sort_normal
25
+ reverse = True if "reverse" in args else False
26
+ if "length" in args:
27
+ sorter = self.sort_length
28
+
29
+ indices = editor.get_lines_with_cursors()
30
+ lines = [editor.get_line(i) for i in indices]
31
+
32
+ sorted_lines = sorted(lines, key=sorter, reverse=reverse)
33
+
34
+ for i, line in enumerate(sorted_lines):
35
+ editor.lines[indices[i]] = line
36
+
37
+
38
+ module = {
39
+ "class": SortLines,
40
+ "name": "sort_lines",
41
+ }
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class Strip(Module):
7
+ """Trim whitespace from start and end of lines."""
8
+
9
+ def run(self, app, editor, args):
10
+ line_nums = editor.get_lines_with_cursors()
11
+ for n in line_nums:
12
+ line = editor.lines[n]
13
+ line.set_data(line.data.strip())
14
+
15
+
16
+ module = {
17
+ "class": Strip,
18
+ "name": "strip",
19
+ }
@@ -0,0 +1,121 @@
1
+ # -*- encoding: utf-8
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from platform import system
7
+
8
+ from suplemon.suplemon_module import Module
9
+
10
+ # Windows and WSL both reach the Windows clipboard through powershell
11
+ POWERSHELL = ["powershell.exe", "-noprofile", "-command"]
12
+
13
+
14
+ def is_wsl():
15
+ """Detect WSL, where the Windows clipboard is reachable via powershell.exe."""
16
+ if system() != "Linux" or not os.path.isfile("/proc/version"):
17
+ return False
18
+ try:
19
+ with open("/proc/version") as f:
20
+ return "microsoft" in f.read().lower()
21
+ except OSError:
22
+ return False
23
+
24
+
25
+ class SystemClipboard(Module):
26
+ """Integrates the system clipboard with suplemon."""
27
+
28
+ def init(self):
29
+ self.init_logging(__name__)
30
+ self.clipboard = self.detect_clipboard()
31
+ if not self.clipboard:
32
+ # No clipboard tool is normal on a headless or minimal system, and
33
+ # suplemon's own copy/paste works regardless, so this isn't a
34
+ # warning. Kept at debug level so it stays out of the way.
35
+ self.logger.debug(
36
+ "No system clipboard tool found. Install 'xsel', 'xclip', 'wl-clipboard', "
37
+ "'pbcopy' or 'termux-api' to share the clipboard with other applications.")
38
+ return False
39
+ self.bind_event_before("insert", self.insert)
40
+ self.bind_event_after("copy", self.copy)
41
+ self.bind_event_after("cut", self.copy)
42
+
43
+ def detect_clipboard(self):
44
+ """Find the commands for the first usable clipboard tool.
45
+
46
+ Ordered so the native mechanism wins where more than one is present:
47
+ Windows and WSL first, then Wayland, then the X11 tools, then Termux.
48
+
49
+ :return: Dict with "get" and "set" commands, or False if none found.
50
+ """
51
+ if (system() == "Windows" or is_wsl()) and shutil.which("powershell.exe"):
52
+ return {
53
+ "get": POWERSHELL + ["Get-Clipboard"],
54
+ "set": POWERSHELL + ["Set-Clipboard"],
55
+ }
56
+ if os.environ.get("WAYLAND_DISPLAY") and shutil.which("wl-copy"):
57
+ return {"get": ["wl-paste", "-n"], "set": ["wl-copy"]}
58
+ # xsel and xclip are X11 clients. Being installed isn't enough: with no
59
+ # DISPLAY they exit with "Can't open display" on every copy and paste.
60
+ if os.environ.get("DISPLAY"):
61
+ if shutil.which("xsel"):
62
+ return {"get": ["xsel", "-b"], "set": ["xsel", "-i", "-b"]}
63
+ if shutil.which("xclip"):
64
+ return {
65
+ "get": ["xclip", "-selection", "clipboard", "-out"],
66
+ "set": ["xclip", "-selection", "clipboard", "-in"],
67
+ }
68
+ if shutil.which("pbcopy"):
69
+ return {"get": ["pbpaste", "-Prefer", "txt"], "set": ["pbcopy"]}
70
+ if shutil.which("termux-clipboard-get"):
71
+ return {"get": ["termux-clipboard-get"], "set": ["termux-clipboard-set"]}
72
+ return False
73
+
74
+ def copy(self, event):
75
+ lines = self.app.get_editor().get_buffer()
76
+ data = "\n".join([str(line) for line in lines])
77
+ self.set_clipboard(data)
78
+
79
+ def insert(self, event):
80
+ data = self.get_clipboard()
81
+ if data is False:
82
+ # Reading the clipboard failed, leave suplemon's own buffer alone
83
+ return
84
+ lines = data.split("\n")
85
+ self.app.get_editor().set_buffer(lines)
86
+
87
+ def get_clipboard(self):
88
+ try:
89
+ # stderr is discarded on purpose. curses owns the screen, so
90
+ # anything a clipboard tool prints lands on top of the editor and
91
+ # corrupts the display.
92
+ return subprocess.check_output(
93
+ self.clipboard["get"],
94
+ stderr=subprocess.DEVNULL,
95
+ universal_newlines=True,
96
+ )
97
+ except (OSError, subprocess.CalledProcessError):
98
+ return False
99
+
100
+ def set_clipboard(self, data):
101
+ try:
102
+ # Both streams go to /dev/null rather than being inherited, for
103
+ # the same reason as in get_clipboard. Not PIPE: xsel forks a
104
+ # background process to own the selection, which keeps a piped
105
+ # stdout open, and communicate() would wait for it forever.
106
+ p = subprocess.Popen(
107
+ self.clipboard["set"],
108
+ stdin=subprocess.PIPE,
109
+ stdout=subprocess.DEVNULL,
110
+ stderr=subprocess.DEVNULL,
111
+ )
112
+ out, err = p.communicate(input=bytes(data, "utf-8"))
113
+ return out
114
+ except (OSError, subprocess.CalledProcessError):
115
+ return False
116
+
117
+
118
+ module = {
119
+ "class": SystemClipboard,
120
+ "name": "system_clipboard",
121
+ }
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class TabsToSpaces(Module):
7
+ """Convert tab characters to spaces in the entire file."""
8
+
9
+ def run(self, app, editor, args):
10
+ for i, line in enumerate(editor.lines):
11
+ new = line.data.replace("\t", " "*editor.config["tab_width"])
12
+ editor.lines[i].set_data(new)
13
+
14
+
15
+ module = {
16
+ "class": TabsToSpaces,
17
+ "name": "tabstospaces",
18
+ }
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class ToggleWhitespace(Module):
7
+ """Toggle visually showing whitespace."""
8
+
9
+ def run(self, app, editor, args):
10
+ # Toggle the boolean
11
+ new_value = not self.app.config["editor"]["show_white_space"]
12
+ self.app.config["editor"]["show_white_space"] = new_value
13
+
14
+
15
+ module = {
16
+ "class": ToggleWhitespace,
17
+ "name": "toggle_whitespace",
18
+ }
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8
2
+
3
+ from suplemon.suplemon_module import Module
4
+
5
+
6
+ class Upper(Module):
7
+ """Transform current lines to upper case."""
8
+
9
+ def run(self, app, editor, args):
10
+ line_nums = []
11
+ for cursor in editor.cursors:
12
+ if cursor.y not in line_nums:
13
+ line_nums.append(cursor.y)
14
+ data = editor.lines[cursor.y].get_data().upper()
15
+ editor.lines[cursor.y].set_data(data)
16
+
17
+
18
+ module = {
19
+ "class": Upper,
20
+ "name": "upper",
21
+ }
suplemon/prompt.py ADDED
@@ -0,0 +1,319 @@
1
+ # -*- encoding: utf-8
2
+ """
3
+ Promts based on the Editor class for querying user input.
4
+ """
5
+
6
+ import os
7
+
8
+ from .editor import Editor
9
+ from .line import Line
10
+
11
+
12
+ class Prompt(Editor):
13
+ """An input prompt based on the Editor."""
14
+ def __init__(self, app, window):
15
+ Editor.__init__(self, app, window)
16
+ self.ready = False
17
+ self.canceled = False
18
+ self.input_func = lambda: False
19
+ self.caption = ""
20
+
21
+ def init(self):
22
+ Editor.init(self)
23
+ # Remove the find feature, otherwise it can be invoked recursively
24
+ del self.operations["find"]
25
+
26
+ def set_config(self, config):
27
+ """Set the configuration for the editor."""
28
+ # Override showing line numbers
29
+ config["show_line_nums"] = False
30
+ Editor.set_config(self, config)
31
+
32
+ def set_input_source(self, input_func):
33
+ # Set the input function to use while looping for input
34
+ self.input_func = input_func
35
+
36
+ def on_ready(self):
37
+ """Accepts the current input."""
38
+ self.ready = True
39
+ return
40
+
41
+ def on_cancel(self):
42
+ """Cancels the input prompt."""
43
+ self.set_data("")
44
+ self.ready = True
45
+ self.canceled = True
46
+ return
47
+
48
+ def line_offset(self):
49
+ """Get the x coordinate of beginning of line."""
50
+ return len(self.caption)+1
51
+
52
+ def render_line_contents(self, line, pos, x_offset, max_len):
53
+ """Render the prompt line."""
54
+ x_offset = self.line_offset()
55
+ # Render the caption
56
+ self.window.addstr(pos[1], 0, self.caption)
57
+ # Render input
58
+ self.render_line_normal(line, pos, x_offset, max_len)
59
+
60
+ def handle_input(self, event):
61
+ """Handle special bindings for the prompt."""
62
+ if event.key_name in ["ctrl+c", "escape"]:
63
+ self.on_cancel()
64
+ return False
65
+ if event.key_name == "enter":
66
+ self.on_ready()
67
+ return False
68
+
69
+ return Editor.handle_input(self, event)
70
+
71
+ def get_input(self, caption="", initial=""):
72
+ """Get text input from the user via the prompt."""
73
+ self.caption = caption
74
+ self.set_data(initial)
75
+ self.end() # Move to the end of the initial text
76
+
77
+ self.refresh()
78
+ success = self.input_loop()
79
+ if success:
80
+ return self.get_data()
81
+ return False
82
+
83
+ def input_loop(self):
84
+ # Run the input loop until ready
85
+ while not self.ready:
86
+ event = self.input_func(True) # blocking
87
+ if event:
88
+ self.handle_input(event)
89
+ self.refresh()
90
+ if not self.canceled:
91
+ return True
92
+
93
+
94
+ class PromptBool(Prompt):
95
+ """An input prompt for booleans based on Prompt."""
96
+
97
+ def set_value(self, value):
98
+ self.value = value
99
+
100
+ def handle_input(self, event):
101
+ """Handle special bindings for the prompt."""
102
+ name = event.key_name
103
+ if name.lower() == "y":
104
+ self.set_value(True)
105
+ self.on_ready()
106
+ return False
107
+ if name.lower() == "n":
108
+ self.set_value(False)
109
+ self.on_ready()
110
+ return False
111
+ Prompt.handle_input(self, event)
112
+
113
+ def get_input(self, caption="", initial=False):
114
+ """Get a boolean value from the user via the prompt."""
115
+
116
+ indicator = "[y/N]"
117
+ if initial:
118
+ indicator = "[Y/n]"
119
+
120
+ self.caption = caption + " " + indicator
121
+ self.set_value(initial)
122
+ self.end() # Move to the end of the initial text
123
+
124
+ self.refresh()
125
+
126
+ # Run the input loop until ready
127
+ success = self.input_loop()
128
+ if success:
129
+ return self.value
130
+ return False
131
+
132
+
133
+ class PromptPassword(Prompt):
134
+ """An input prompt for passwords based on Prompt."""
135
+
136
+ def render_line_contents(self, line, pos, x_offset, max_len):
137
+ obscured = Line("*" * len(line))
138
+ Prompt.render_line_contents(self, obscured, pos, x_offset, max_len)
139
+
140
+
141
+ class PromptFiltered(Prompt):
142
+ """An input prompt that allows intercepting and filtering input events."""
143
+
144
+ def __init__(self, app, window, handler=None):
145
+ Prompt.__init__(self, app, window)
146
+ self.prompt_handler = handler
147
+
148
+ def handle_input(self, event):
149
+ """Handle special bindings for the prompt."""
150
+ # The cancel and accept keys are kept for concistency
151
+ if event.key_name in ["ctrl+c", "escape"]:
152
+ self.on_cancel()
153
+ return False
154
+ if event.key_name == "enter":
155
+ self.on_ready()
156
+ return False
157
+
158
+ if self.prompt_handler and self.prompt_handler(self, event):
159
+ # If the prompt handler returns True the default action is skipped
160
+ return True
161
+
162
+ return Editor.handle_input(self, event)
163
+
164
+
165
+ class PromptAutocmp(Prompt):
166
+ """An input prompt with basic autocompletion."""
167
+
168
+ def __init__(self, app, window, initial_items=None):
169
+ Prompt.__init__(self, app, window)
170
+ # Whether the autocomplete feature is active
171
+ self.complete_active = False
172
+ # Index of last item that was autocompleted
173
+ self.complete_index = 0
174
+ # Input data to use for autocompletion (stored when autocompletion is activated)
175
+ self.complete_data = ""
176
+ # Default autocompletable items
177
+ self.complete_items = [] if initial_items is None else initial_items
178
+
179
+ def handle_input(self, event):
180
+ """Handle special bindings for the prompt."""
181
+ name = event.key_name
182
+ if self.complete_active:
183
+ # Allow accepting completed directories with enter
184
+ if name == "enter":
185
+ if self.has_match():
186
+ self.deactivate_autocomplete()
187
+ return False
188
+ # Revert autocompletion with esc
189
+ if name == "escape":
190
+ self.revert_autocomplete()
191
+ self.deactivate_autocomplete()
192
+ return False
193
+ if name == "tab":
194
+ # Run autocompletion when tab is pressed
195
+ self.autocomplete()
196
+ # Don't pass the event to the parent class
197
+ return False
198
+ elif name == "shift+tab":
199
+ # Go to previous item when shift+tab is pressed
200
+ self.autocomplete(previous=True)
201
+ # Don't pass the event to the parent class
202
+ return False
203
+ else:
204
+ # If any key other than tab is pressed deactivate the autocompleter
205
+ self.deactivate_autocomplete()
206
+ Prompt.handle_input(self, event)
207
+
208
+ def autocomplete(self, previous=False):
209
+ data = self.get_data() # Use the current input by default
210
+ if self.complete_active: # If the completer is active use the its initial input value
211
+ data = self.complete_data
212
+
213
+ name = self.get_completable_name(data)
214
+ items = self.get_completable_items(data)
215
+
216
+ # Filter the items by name if the input path contains a name
217
+ if name:
218
+ items = self.filter_items(items, name)
219
+ if not items:
220
+ # Deactivate completion if there's nothing to complete
221
+ self.deactivate_autocomplete()
222
+ return False
223
+
224
+ if not self.complete_active:
225
+ # Initialize the autocompletor
226
+ self.complete_active = True
227
+ self.complete_data = data
228
+ self.complete_index = 0
229
+ else:
230
+ # Increment the selected item countor
231
+ if previous:
232
+ # Go back
233
+ self.complete_index -= 1
234
+ if self.complete_index < 0:
235
+ self.complete_index = len(items)-1
236
+ else:
237
+ # Go forward
238
+ self.complete_index += 1
239
+ if self.complete_index > len(items)-1:
240
+ self.complete_index = 0
241
+
242
+ item = items[self.complete_index]
243
+ new_data = self.get_full_completion(data, item)
244
+ if len(items) == 1:
245
+ self.deactivate_autocomplete()
246
+ # Set the input data to the completion and move cursor to the end
247
+ self.set_data(new_data)
248
+ self.end()
249
+
250
+ def get_completable_name(self, data=""):
251
+ return data
252
+
253
+ def get_completable_items(self, data=""):
254
+ return self.complete_items
255
+
256
+ def get_full_completion(self, data, item):
257
+ return item
258
+
259
+ def has_match(self):
260
+ return False
261
+
262
+ def deactivate_autocomplete(self):
263
+ self.complete_active = False
264
+ self.complete_index = 0
265
+ self.complete_data = ""
266
+
267
+ def revert_autocomplete(self):
268
+ if self.complete_data:
269
+ self.set_data(self.complete_data)
270
+
271
+ def filter_items(self, items, name):
272
+ """Remove items that don't begin with name. Not case sensitive."""
273
+ if not name:
274
+ return items
275
+ name = name.lower()
276
+ return [item for item in items if item.lower().startswith(name)]
277
+
278
+
279
+ class PromptFile(PromptAutocmp):
280
+ """An input prompt with path autocompletion based on PromptAutocmp."""
281
+
282
+ def __init__(self, app, window):
283
+ PromptAutocmp.__init__(self, app, window)
284
+
285
+ def has_match(self):
286
+ return os.path.isdir(os.path.expanduser(self.get_data()))
287
+
288
+ def get_completable_name(self, data=""):
289
+ return os.path.basename(data)
290
+
291
+ def get_completable_items(self, data=""):
292
+ return self.get_path_contents(data) # Get directory listing of input path
293
+
294
+ def get_full_completion(self, data, item):
295
+ return os.path.join(os.path.dirname(data), item)
296
+
297
+ def get_path_contents(self, path):
298
+ path = os.path.dirname(os.path.expanduser(path))
299
+ # If we get an empty path use the current directory
300
+ if not path:
301
+ try:
302
+ path = os.getcwd()
303
+ except FileNotFoundError:
304
+ # This might happen if the cwd has been
305
+ # removed after starting suplemon.
306
+ return []
307
+ # In case we don't have sufficent permissions
308
+ try:
309
+ contents = os.listdir(path)
310
+ except OSError:
311
+ return []
312
+ contents.sort()
313
+ items = []
314
+ # Append directory separator to directories
315
+ for item in contents:
316
+ if os.path.isdir(os.path.join(path, item)):
317
+ item = item + os.sep
318
+ items.append(item)
319
+ return items