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,59 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon import helpers
|
|
4
|
+
from suplemon.suplemon_module import Module
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Comment(Module):
|
|
8
|
+
"""Toggle line commenting based on current file syntax."""
|
|
9
|
+
|
|
10
|
+
def run(self, app, editor, args):
|
|
11
|
+
"""Comment the current line(s)."""
|
|
12
|
+
try:
|
|
13
|
+
# Try to get comment start and end syntax
|
|
14
|
+
comment = editor.syntax.get_comment()
|
|
15
|
+
except AttributeError:
|
|
16
|
+
return False
|
|
17
|
+
line_nums = editor.get_lines_with_cursors()
|
|
18
|
+
# Iterate through lines
|
|
19
|
+
for lnum in line_nums:
|
|
20
|
+
line = editor.lines[lnum]
|
|
21
|
+
if not len(line):
|
|
22
|
+
continue # Skip empty lines
|
|
23
|
+
# Look for comment syntax in stripped line (TODO:Make this smarter)
|
|
24
|
+
target = str(line).strip()
|
|
25
|
+
w = helpers.whitespace(line) # Amount of whitespace at line start
|
|
26
|
+
indent = helpers.leading_whitespace(line) # That whitespace as written
|
|
27
|
+
# If the line starts with comment syntax
|
|
28
|
+
if target.startswith(comment[0]):
|
|
29
|
+
# Reconstruct the whitespace and add the line
|
|
30
|
+
new_line = indent + line[w+len(comment[0]):]
|
|
31
|
+
# If comment end syntax exists
|
|
32
|
+
if comment[1]:
|
|
33
|
+
# Try to remove it from the end of the line
|
|
34
|
+
if new_line.endswith(comment[1]):
|
|
35
|
+
new_line = new_line[:-1*len(comment[1])]
|
|
36
|
+
# Store the modified line
|
|
37
|
+
# editor.lines[lnum] = Line(new_line)
|
|
38
|
+
editor.lines[lnum].set_data(new_line)
|
|
39
|
+
# If the line isn't commented
|
|
40
|
+
else:
|
|
41
|
+
# Slice out the prepended whitespace
|
|
42
|
+
new_line = line[w:]
|
|
43
|
+
# Add the whitespace and starting comment
|
|
44
|
+
new_line = indent + comment[0] + new_line
|
|
45
|
+
if comment[1]:
|
|
46
|
+
# Add comment end syntax if needed
|
|
47
|
+
new_line += comment[1]
|
|
48
|
+
# Store modified line
|
|
49
|
+
# editor.lines[lnum] = Line(new_line)
|
|
50
|
+
editor.lines[lnum].set_data(new_line)
|
|
51
|
+
# Keep cursors under control, same as usual...
|
|
52
|
+
editor.move_cursors()
|
|
53
|
+
editor.store_action_state("comment")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
module = {
|
|
57
|
+
"class": Comment,
|
|
58
|
+
"name": "comment",
|
|
59
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from suplemon import config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SuplemonConfig(config.ConfigModule):
|
|
9
|
+
"""Shortcut for openning the config files."""
|
|
10
|
+
|
|
11
|
+
def init(self):
|
|
12
|
+
self.config_name = "defaults.json"
|
|
13
|
+
self.config_default_path = os.path.join(self.app.path, "config", self.config_name)
|
|
14
|
+
self.config_user_path = self.app.config.path()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
module = {
|
|
18
|
+
"class": SuplemonConfig,
|
|
19
|
+
"name": "config",
|
|
20
|
+
}
|
suplemon/modules/date.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from suplemon.suplemon_module import Module
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Date(Module):
|
|
9
|
+
"""Shows the current date without year in the top status bar."""
|
|
10
|
+
|
|
11
|
+
def get_status(self):
|
|
12
|
+
s = time.strftime("%Y-%m-%d")
|
|
13
|
+
if self.app.config["app"]["use_unicode_symbols"]:
|
|
14
|
+
return "" + s
|
|
15
|
+
return s
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module = {
|
|
19
|
+
"class": Date,
|
|
20
|
+
"name": "date",
|
|
21
|
+
"status": "top",
|
|
22
|
+
}
|
suplemon/modules/diff.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
|
|
5
|
+
from suplemon.suplemon_module import Module
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Diff(Module):
|
|
9
|
+
"""View a diff of the current file compared to it's on disk version."""
|
|
10
|
+
|
|
11
|
+
def run(self, app, editor, args):
|
|
12
|
+
curr_file = app.get_file()
|
|
13
|
+
curr_path = curr_file.get_path()
|
|
14
|
+
if not curr_path:
|
|
15
|
+
self.app.set_status("File hasn't been saved, can't show diff.")
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
current_data = editor.get_data()
|
|
19
|
+
f = open(curr_path)
|
|
20
|
+
original_data = f.read()
|
|
21
|
+
f.close()
|
|
22
|
+
diff = self.get_diff(original_data, current_data)
|
|
23
|
+
|
|
24
|
+
if not diff:
|
|
25
|
+
self.app.set_status("The file in the editor and on disk are identical.")
|
|
26
|
+
return False
|
|
27
|
+
file = app.new_file()
|
|
28
|
+
file.set_name(curr_file.get_name() + ".diff")
|
|
29
|
+
file.set_data(diff)
|
|
30
|
+
app.switch_to_file(app.last_file_index())
|
|
31
|
+
|
|
32
|
+
def get_diff(self, a, b):
|
|
33
|
+
a = a.splitlines(1)
|
|
34
|
+
b = b.splitlines(1)
|
|
35
|
+
diff = difflib.unified_diff(a, b)
|
|
36
|
+
return "".join(diff)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
module = {
|
|
40
|
+
"class": Diff,
|
|
41
|
+
"name": "diff",
|
|
42
|
+
}
|
suplemon/modules/eval.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Eval(Module):
|
|
7
|
+
"""
|
|
8
|
+
Evaluate a python expression and show the result in the status bar.
|
|
9
|
+
|
|
10
|
+
If no expression is provided the current line(s) are evaluated and
|
|
11
|
+
replaced with the evaluation result.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def run(self, app, editor, args):
|
|
15
|
+
if not args:
|
|
16
|
+
return self.evaluate_lines(editor)
|
|
17
|
+
else:
|
|
18
|
+
return self.evaluate_input(args)
|
|
19
|
+
|
|
20
|
+
def evaluate_input(self, inp):
|
|
21
|
+
try:
|
|
22
|
+
value = eval(inp)
|
|
23
|
+
except Exception:
|
|
24
|
+
self.app.set_status("Eval failed.")
|
|
25
|
+
return False
|
|
26
|
+
self.app.set_status("Result:{0}".format(value))
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
def evaluate_lines(self, editor):
|
|
30
|
+
line_nums = editor.get_lines_with_cursors()
|
|
31
|
+
for num in line_nums:
|
|
32
|
+
line = editor.get_line(num)
|
|
33
|
+
try:
|
|
34
|
+
value = eval(line.get_data())
|
|
35
|
+
except Exception:
|
|
36
|
+
continue
|
|
37
|
+
line.set_data(str(value))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
module = {
|
|
41
|
+
"class": Eval,
|
|
42
|
+
"name": "eval",
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
|
|
5
|
+
from suplemon.suplemon_module import Module
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Hostname(Module):
|
|
9
|
+
"""Shows the machine hostname in the bottom status bar."""
|
|
10
|
+
|
|
11
|
+
def init(self):
|
|
12
|
+
self.hostname = ""
|
|
13
|
+
hostinfo = None
|
|
14
|
+
try:
|
|
15
|
+
hostinfo = socket.gethostbyaddr(socket.gethostname())
|
|
16
|
+
except OSError:
|
|
17
|
+
self.logger.debug("Failed to get hostname.")
|
|
18
|
+
if hostinfo:
|
|
19
|
+
self.hostname = hostinfo[0]
|
|
20
|
+
# Use shorter hostname if available
|
|
21
|
+
if hostinfo[1]:
|
|
22
|
+
self.hostname = hostinfo[1][0]
|
|
23
|
+
|
|
24
|
+
def get_status(self):
|
|
25
|
+
if self.hostname:
|
|
26
|
+
return "host:{0}".format(self.hostname)
|
|
27
|
+
return ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
module = {
|
|
31
|
+
"class": Hostname,
|
|
32
|
+
"name": "hostname",
|
|
33
|
+
"status": "bottom",
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InputTest(Module):
|
|
7
|
+
"""
|
|
8
|
+
Input event testing utility.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def handler(self, prompt, event):
|
|
12
|
+
self.app.set_status("InputEvent:" + str(event))
|
|
13
|
+
prompt.on_ready()
|
|
14
|
+
return True # Disable normal key handling
|
|
15
|
+
|
|
16
|
+
def run(self, app, editor, args):
|
|
17
|
+
app.ui.query_filtered("Waiting for input...", handler=self.handler)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
module = {
|
|
21
|
+
"class": InputTest,
|
|
22
|
+
"name": "input_test",
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from suplemon import config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeymapConfig(config.ConfigModule):
|
|
9
|
+
"""Shortcut to openning the keymap config file."""
|
|
10
|
+
|
|
11
|
+
def init(self):
|
|
12
|
+
self.config_name = "keymap.json"
|
|
13
|
+
self.config_default_path = os.path.join(self.app.path, "config", self.config_name)
|
|
14
|
+
self.config_user_path = self.app.config.keymap_path()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
module = {
|
|
18
|
+
"class": KeymapConfig,
|
|
19
|
+
"name": "keymap",
|
|
20
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
from suplemon.suplemon_module import Module
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Linter(Module):
|
|
12
|
+
"""Linter for suplemon."""
|
|
13
|
+
|
|
14
|
+
def init(self):
|
|
15
|
+
self.init_logging(__name__)
|
|
16
|
+
|
|
17
|
+
# TODO: Run linting in a seperate thread to avoid
|
|
18
|
+
# blocking the UI when the app is loading
|
|
19
|
+
|
|
20
|
+
# Lint all files after app is loaded
|
|
21
|
+
self.bind_event_after("app_loaded", self.on_loaded)
|
|
22
|
+
# Show linting messages in status bar
|
|
23
|
+
self.bind_event_after("mainloop", self.mainloop)
|
|
24
|
+
# Re-lint current file when appropriate
|
|
25
|
+
self.bind_event_after("save_file", self.lint_current_file)
|
|
26
|
+
self.bind_event_after("save_file_as", self.lint_current_file)
|
|
27
|
+
self.bind_event_after("reload_file", self.lint_current_file)
|
|
28
|
+
self.bind_event_after("open_file", self.lint_current_file)
|
|
29
|
+
|
|
30
|
+
def run(self, app, editor, args):
|
|
31
|
+
"""Run the linting command."""
|
|
32
|
+
editor = self.app.get_file().get_editor()
|
|
33
|
+
count = self.get_msg_count(editor)
|
|
34
|
+
status = "{0} lines with linting errors in this file.".format(str(count))
|
|
35
|
+
self.app.set_status(status)
|
|
36
|
+
|
|
37
|
+
def on_loaded(self, event):
|
|
38
|
+
# Lint all files in a thread since it might take a while
|
|
39
|
+
thread = threading.Thread(target=self.lint_all_files, args=(event,))
|
|
40
|
+
# Use daemon mode to forcefully shutdown the thread on application exit.
|
|
41
|
+
# Python2 doesn't know the boolean daemon keyword argument to the Thread()
|
|
42
|
+
# constructor so we use the .daemon property instead.
|
|
43
|
+
thread.daemon = True
|
|
44
|
+
thread.start()
|
|
45
|
+
|
|
46
|
+
def mainloop(self, event):
|
|
47
|
+
"""Run the linting command."""
|
|
48
|
+
file = self.app.get_file()
|
|
49
|
+
editor = file.get_editor()
|
|
50
|
+
cursor = editor.get_cursor()
|
|
51
|
+
if len(editor.cursors) > 1:
|
|
52
|
+
return False
|
|
53
|
+
line_no = cursor.y + 1
|
|
54
|
+
msg = self.get_msgs_on_line(editor, cursor.y)
|
|
55
|
+
if msg:
|
|
56
|
+
self.app.set_status("Line {0}: {1}".format(str(line_no), msg))
|
|
57
|
+
|
|
58
|
+
def lint_current_file(self, event):
|
|
59
|
+
self.lint_file(self.app.get_file())
|
|
60
|
+
|
|
61
|
+
def lint_all_files(self, event):
|
|
62
|
+
"""Do linting check for all open files and store results."""
|
|
63
|
+
for file in self.app.files:
|
|
64
|
+
self.lint_file(file)
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def lint_file(self, file):
|
|
68
|
+
path = file.get_path()
|
|
69
|
+
if not path: # Unsaved file
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
ext = file.get_extension().lower()
|
|
73
|
+
linter = False
|
|
74
|
+
if ext == "py":
|
|
75
|
+
linter = PyLint(self.logger)
|
|
76
|
+
elif ext == "js":
|
|
77
|
+
linter = JsLint(self.logger)
|
|
78
|
+
elif ext == "php":
|
|
79
|
+
linter = PhpLint(self.logger)
|
|
80
|
+
|
|
81
|
+
if not linter:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
linting = linter.lint(path)
|
|
85
|
+
if linting is False:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
editor = file.get_editor()
|
|
89
|
+
for line_no in range(len(editor.lines)):
|
|
90
|
+
line = editor.lines[line_no]
|
|
91
|
+
if line_no+1 in linting.keys():
|
|
92
|
+
line.linting = linting[line_no+1]
|
|
93
|
+
line.set_number_color(1)
|
|
94
|
+
else:
|
|
95
|
+
line.linting = False
|
|
96
|
+
line.reset_number_color()
|
|
97
|
+
|
|
98
|
+
def get_msgs_on_line(self, editor, line_no):
|
|
99
|
+
line = editor.lines[line_no]
|
|
100
|
+
if not hasattr(line, "linting") or not line.linting:
|
|
101
|
+
return False
|
|
102
|
+
return line.linting[0][1]
|
|
103
|
+
|
|
104
|
+
def get_msg_count(self, editor):
|
|
105
|
+
count = 0
|
|
106
|
+
for line in editor.lines:
|
|
107
|
+
if hasattr(line, "linting"):
|
|
108
|
+
if line.linting:
|
|
109
|
+
count += 1
|
|
110
|
+
return count
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BaseLint:
|
|
114
|
+
def __init__(self, logger):
|
|
115
|
+
self.logger = logger
|
|
116
|
+
|
|
117
|
+
def lint(self, path):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
def get_file_linting(self, path):
|
|
121
|
+
"""Do linting check for given file path."""
|
|
122
|
+
return {}
|
|
123
|
+
|
|
124
|
+
def get_output(self, cmd):
|
|
125
|
+
try:
|
|
126
|
+
fnull = open(os.devnull, "w")
|
|
127
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=fnull)
|
|
128
|
+
fnull.close()
|
|
129
|
+
except OSError:
|
|
130
|
+
self.logger.debug("Subprocess failed.")
|
|
131
|
+
return False
|
|
132
|
+
out, err = process.communicate()
|
|
133
|
+
return out
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class PyLint(BaseLint):
|
|
137
|
+
def __init__(self, logger):
|
|
138
|
+
BaseLint.__init__(self, logger)
|
|
139
|
+
# Error codes to ignore e.g. 'E501' (line too long)
|
|
140
|
+
self.ignore = []
|
|
141
|
+
# Max length of line
|
|
142
|
+
self.max_line_length = 120 # Default is 79
|
|
143
|
+
|
|
144
|
+
def lint(self, path):
|
|
145
|
+
if not self.has_flake8_support():
|
|
146
|
+
self.logger.warning("Flake8 not available. Can't show linting.")
|
|
147
|
+
return False
|
|
148
|
+
self.logger.debug(self.has_flake8_support())
|
|
149
|
+
return self.get_file_linting(path)
|
|
150
|
+
|
|
151
|
+
def has_flake8_support(self):
|
|
152
|
+
output = self.get_output(["flake8", "--version"])
|
|
153
|
+
return output
|
|
154
|
+
|
|
155
|
+
def get_file_linting(self, path):
|
|
156
|
+
"""Do linting check for given file path."""
|
|
157
|
+
output = self.get_output(["flake8", "--max-line-length", str(self.max_line_length), path])
|
|
158
|
+
if output is False:
|
|
159
|
+
self.logger.warning("Failed to get linting for file '{0}'.".format(path))
|
|
160
|
+
return False
|
|
161
|
+
output = output.decode("utf-8")
|
|
162
|
+
# Remove file paths from output
|
|
163
|
+
output = output.replace(path+":", "")
|
|
164
|
+
lines = output.split("\n")
|
|
165
|
+
linting = {}
|
|
166
|
+
for line in lines:
|
|
167
|
+
if not line:
|
|
168
|
+
continue
|
|
169
|
+
try:
|
|
170
|
+
parts = line.split(":")
|
|
171
|
+
line_no = int(parts[0])
|
|
172
|
+
char_no = int(parts[1])
|
|
173
|
+
data = ":".join(parts[2:]).strip()
|
|
174
|
+
err_code = data.split(" ")[0]
|
|
175
|
+
if err_code in self.ignore:
|
|
176
|
+
continue
|
|
177
|
+
if line_no not in linting.keys():
|
|
178
|
+
linting[line_no] = []
|
|
179
|
+
linting[line_no].append((char_no, data, err_code))
|
|
180
|
+
except (IndexError, ValueError):
|
|
181
|
+
self.logger.debug("Failed to parse line:{0}".format(line))
|
|
182
|
+
return linting
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class JsLint(BaseLint):
|
|
186
|
+
def __init__(self, logger):
|
|
187
|
+
BaseLint.__init__(self, logger)
|
|
188
|
+
|
|
189
|
+
def lint(self, path):
|
|
190
|
+
return self.get_file_linting(path)
|
|
191
|
+
|
|
192
|
+
def get_file_linting(self, path):
|
|
193
|
+
"""Do linting check for given file path."""
|
|
194
|
+
output = self.get_output(["jshint", path])
|
|
195
|
+
if output is False:
|
|
196
|
+
self.logger.warning("Failed to get linting for file '{0}'.".format(path))
|
|
197
|
+
return False
|
|
198
|
+
output = output.decode("utf-8")
|
|
199
|
+
# Remove file paths from output
|
|
200
|
+
output = output.replace(path+": ", "")
|
|
201
|
+
lines = output.split("\n")
|
|
202
|
+
linting = {}
|
|
203
|
+
for line in lines:
|
|
204
|
+
if not line:
|
|
205
|
+
continue
|
|
206
|
+
try:
|
|
207
|
+
parts = line.split(", ")
|
|
208
|
+
if len(parts) < 3:
|
|
209
|
+
continue
|
|
210
|
+
line_no = int(re.sub(r"\D", "", parts[0]))
|
|
211
|
+
char_no = int(re.sub(r"\D", "", parts[1]))
|
|
212
|
+
data = parts[2]
|
|
213
|
+
err_code = None
|
|
214
|
+
if line_no not in linting.keys():
|
|
215
|
+
linting[line_no] = []
|
|
216
|
+
linting[line_no].append((char_no, data, err_code))
|
|
217
|
+
except (IndexError, ValueError):
|
|
218
|
+
self.logger.debug("Failed to parse line:{0}".format(line))
|
|
219
|
+
return linting
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class PhpLint(BaseLint):
|
|
223
|
+
def __init__(self, logger):
|
|
224
|
+
BaseLint.__init__(self, logger)
|
|
225
|
+
|
|
226
|
+
def lint(self, path):
|
|
227
|
+
return self.get_file_linting(path)
|
|
228
|
+
|
|
229
|
+
def get_output(self, cmd, errors=False):
|
|
230
|
+
# Need to override this since php reports the errors to stderr
|
|
231
|
+
try:
|
|
232
|
+
fnull = open(os.devnull, "w")
|
|
233
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
234
|
+
fnull.close()
|
|
235
|
+
except OSError:
|
|
236
|
+
self.logger.debug("Subprocess failed.")
|
|
237
|
+
return False
|
|
238
|
+
out, err = process.communicate()
|
|
239
|
+
return err
|
|
240
|
+
|
|
241
|
+
def get_file_linting(self, path):
|
|
242
|
+
"""Do linting check for given file path."""
|
|
243
|
+
output = self.get_output(["php", "-l", path])
|
|
244
|
+
if output is False:
|
|
245
|
+
self.logger.warning("Failed to get linting for file '{0}'.".format(path))
|
|
246
|
+
return False
|
|
247
|
+
output = output.decode("utf-8")
|
|
248
|
+
lines = output.split("\n")
|
|
249
|
+
linting = {}
|
|
250
|
+
for line in lines:
|
|
251
|
+
line = line.strip()
|
|
252
|
+
if not line:
|
|
253
|
+
continue
|
|
254
|
+
try:
|
|
255
|
+
parts = line.split(" in {0} on line ".format(path))
|
|
256
|
+
self.logger.debug(parts)
|
|
257
|
+
if len(parts) != 2:
|
|
258
|
+
continue
|
|
259
|
+
line_no = int(parts[1])
|
|
260
|
+
char_no = 0
|
|
261
|
+
data = parts[0]
|
|
262
|
+
err_code = None
|
|
263
|
+
if line_no not in linting.keys():
|
|
264
|
+
linting[line_no] = []
|
|
265
|
+
linting[line_no].append((char_no, data, err_code))
|
|
266
|
+
except (IndexError, ValueError):
|
|
267
|
+
self.logger.debug("Failed to parse line:{0}".format(line))
|
|
268
|
+
return linting
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
module = {
|
|
272
|
+
"class": Linter,
|
|
273
|
+
"name": "linter",
|
|
274
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Lower(Module):
|
|
7
|
+
"""Transform current lines to lower 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
|
+
new_data = editor.lines[cursor.y].get_data().lower()
|
|
15
|
+
editor.lines[cursor.y].data = new_data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module = {
|
|
19
|
+
"class": Lower,
|
|
20
|
+
"name": "lower",
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LStrip(Module):
|
|
7
|
+
"""Trim whitespace from beginning of current lines."""
|
|
8
|
+
|
|
9
|
+
def run(self, app, editor, args):
|
|
10
|
+
# TODO: move cursors in sync with line contents
|
|
11
|
+
line_nums = editor.get_lines_with_cursors()
|
|
12
|
+
for n in line_nums:
|
|
13
|
+
line = editor.lines[n]
|
|
14
|
+
line.data = line.data.lstrip()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
module = {
|
|
18
|
+
"class": LStrip,
|
|
19
|
+
"name": "lstrip",
|
|
20
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Paste(Module):
|
|
7
|
+
"""Toggle paste mode (helpful when pasting over SSH if auto indent is enabled)"""
|
|
8
|
+
|
|
9
|
+
def init(self):
|
|
10
|
+
# Flag for paste mode
|
|
11
|
+
self.active = False
|
|
12
|
+
# Initial state of auto indent
|
|
13
|
+
self.auto_indent_active = self.app.config["editor"]["auto_indent_newline"]
|
|
14
|
+
# Listen for config changes
|
|
15
|
+
self.bind_event_after("config_loaded", self.config_loaded)
|
|
16
|
+
|
|
17
|
+
def run(self, app, editor, args):
|
|
18
|
+
# Simply toggle pastemode when the command is run
|
|
19
|
+
self.active = not self.active
|
|
20
|
+
self.set_paste_mode(self.active)
|
|
21
|
+
self.show_confirmation()
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
def config_loaded(self, e):
|
|
25
|
+
# Refresh the auto indent state when config is reloaded
|
|
26
|
+
self.auto_indent_active = self.app.config["editor"]["auto_indent_newline"]
|
|
27
|
+
|
|
28
|
+
def get_status(self):
|
|
29
|
+
# Return the paste mode status for the statusbar
|
|
30
|
+
return "[PASTEMODE]" if self.active else ""
|
|
31
|
+
|
|
32
|
+
def show_confirmation(self):
|
|
33
|
+
# Show a status message when pastemode is toggled
|
|
34
|
+
state = "activated" if self.active else "deactivated"
|
|
35
|
+
self.app.set_status("Paste mode " + state)
|
|
36
|
+
|
|
37
|
+
def set_paste_mode(self, active):
|
|
38
|
+
# Enable or disable auto indent
|
|
39
|
+
if active:
|
|
40
|
+
self.app.config["editor"]["auto_indent_newline"] = False
|
|
41
|
+
else:
|
|
42
|
+
self.app.config["editor"]["auto_indent_newline"] = self.auto_indent_active
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
module = {
|
|
46
|
+
"class": Paste,
|
|
47
|
+
"name": "paste",
|
|
48
|
+
"status": "bottom",
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Reload(Module):
|
|
7
|
+
"""Reload all add-on modules."""
|
|
8
|
+
|
|
9
|
+
def run(self, app, editor, args):
|
|
10
|
+
self.app.modules.load()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
module = {
|
|
14
|
+
"class": Reload,
|
|
15
|
+
"name": "reload",
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
|
|
3
|
+
from suplemon.suplemon_module import Module
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ReplaceAll(Module):
|
|
7
|
+
"""Replace all occurrences in all files of given text with given replacement."""
|
|
8
|
+
|
|
9
|
+
def run(self, app, editor, args):
|
|
10
|
+
r_from = self.app.ui.query("Replace text:")
|
|
11
|
+
if not r_from:
|
|
12
|
+
return False
|
|
13
|
+
r_to = self.app.ui.query("Replace with:")
|
|
14
|
+
if not r_to:
|
|
15
|
+
return False
|
|
16
|
+
for file in app.get_files():
|
|
17
|
+
file.editor.replace_all(r_from, r_to)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
module = {
|
|
21
|
+
"class": ReplaceAll,
|
|
22
|
+
"name": "replace_all",
|
|
23
|
+
}
|