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
suplemon/__init__.py
ADDED
|
File without changes
|
suplemon/__main__.py
ADDED
suplemon/cli.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- encoding: utf-8
|
|
3
|
+
"""
|
|
4
|
+
Start a Suplemon instance in the current window
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import argparse
|
|
11
|
+
except ImportError:
|
|
12
|
+
# Python < 2.7
|
|
13
|
+
argparse = False
|
|
14
|
+
|
|
15
|
+
from .main import App, __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_debug_notice(app):
|
|
19
|
+
"""Explain the log that debug mode just printed, and how to turn it off.
|
|
20
|
+
|
|
21
|
+
Without this the messages look like something went wrong, when they are
|
|
22
|
+
only shown because debug mode asked for them.
|
|
23
|
+
"""
|
|
24
|
+
print(
|
|
25
|
+
"\nThe messages above are Suplemon's debug log, shown because debug mode is on.\n"
|
|
26
|
+
'Set "debug": false in {0} to stop showing them.'.format(app.config.path()),
|
|
27
|
+
file=sys.stderr
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
"""Handle CLI invocation"""
|
|
33
|
+
# Parse our CLI arguments
|
|
34
|
+
config_file = None
|
|
35
|
+
log_level = None
|
|
36
|
+
if argparse:
|
|
37
|
+
parser = argparse.ArgumentParser(description="Console text editor with multi cursor support")
|
|
38
|
+
parser.add_argument("filenames", metavar="filename", type=str, nargs="*", help="files to open")
|
|
39
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
40
|
+
parser.add_argument("--config", type=str, help="configuration file path")
|
|
41
|
+
parser.add_argument("--log-level", type=int, help="debug logging level")
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
filenames = args.filenames
|
|
44
|
+
config_file = args.config
|
|
45
|
+
log_level = args.log_level
|
|
46
|
+
else:
|
|
47
|
+
# Python < 2.7 fallback
|
|
48
|
+
filenames = sys.argv[1:]
|
|
49
|
+
|
|
50
|
+
# Generate and start our application
|
|
51
|
+
app = App(filenames=filenames, config_file=config_file, log_level=log_level)
|
|
52
|
+
if app.init():
|
|
53
|
+
app.run()
|
|
54
|
+
|
|
55
|
+
# Output log info
|
|
56
|
+
if app.debug:
|
|
57
|
+
for logger_handler in app.logger.handlers:
|
|
58
|
+
logger_handler.close()
|
|
59
|
+
# Only explain the log if there was actually something to show
|
|
60
|
+
if any(getattr(handler, "flushed_records", 0) for handler in app.logger.handlers):
|
|
61
|
+
print_debug_notice(app)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Suplemon Default Config
|
|
2
|
+
|
|
3
|
+
// This file contains the default config for Suplemon and should not be edited.
|
|
4
|
+
// If the file doesn't exist, or if it has errors Suplemon can't run.
|
|
5
|
+
// Suplemon supports single line comments in JSON as seen here.
|
|
6
|
+
|
|
7
|
+
// There are three main groups for settings:
|
|
8
|
+
// - app: Global settings
|
|
9
|
+
// - editor: Editor behaviour
|
|
10
|
+
// - display: How the UI looks
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
// Global settings
|
|
14
|
+
"app": {
|
|
15
|
+
// Print the debug log to the terminal when Suplemon exits.
|
|
16
|
+
// The log is written to output.log next to this file either way.
|
|
17
|
+
"debug": false,
|
|
18
|
+
// Debug log level (0: Notset, 10: Debug, 20: Info, 30: Warning, 40: Error, 50: Critical)
|
|
19
|
+
"debug_level": 20,
|
|
20
|
+
// How long curses will wait to detect ESC key
|
|
21
|
+
"escdelay": 50,
|
|
22
|
+
// Whether to use special unicode symbols for decoration
|
|
23
|
+
"use_unicode_symbols": true,
|
|
24
|
+
// If your $TERM ends in -256color and this is true, 'xterm-256color'
|
|
25
|
+
// will be used instead, working around an issue with curses.
|
|
26
|
+
"imitate_256color": false
|
|
27
|
+
},
|
|
28
|
+
// Editor settings
|
|
29
|
+
"editor": {
|
|
30
|
+
// Indent new lines to same level as previous line
|
|
31
|
+
"auto_indent_newline": true,
|
|
32
|
+
// Character to use for end of line
|
|
33
|
+
"end_of_line": "\n",
|
|
34
|
+
// Unindent with backspace
|
|
35
|
+
"backspace_unindent": true,
|
|
36
|
+
// Cursor style. 'reverse' or 'underline'
|
|
37
|
+
"cursor_style": "reverse",
|
|
38
|
+
// Encoding for reading and writing files
|
|
39
|
+
"default_encoding": "utf-8",
|
|
40
|
+
// Use hard tabs (insert actual tabulator character instead of spaces)
|
|
41
|
+
"hard_tabs": 0,
|
|
42
|
+
// Number of spaces to insert when pressing tab
|
|
43
|
+
"tab_width": 4,
|
|
44
|
+
// Amount of undo states to store
|
|
45
|
+
"max_history": 50,
|
|
46
|
+
// Characters considered to separate words
|
|
47
|
+
"punctuation": " (){}[]<>$@!%'\"=+-/*.:,;_\n\r",
|
|
48
|
+
// Character drawn at the end of each line when line ends are shown.
|
|
49
|
+
// Line ends are hidden at startup; F10 toggles them.
|
|
50
|
+
// Default is U+21B5, a downwards arrow with tip leftwards.
|
|
51
|
+
"line_end_char": "\u21B5",
|
|
52
|
+
// White space characters and their visual matches
|
|
53
|
+
"white_space_map": {
|
|
54
|
+
// Null byte as null symbol
|
|
55
|
+
"\u0000": "\u2400",
|
|
56
|
+
// Space as interpunct
|
|
57
|
+
" ": "\u00B7",
|
|
58
|
+
// Tab as tab symbol
|
|
59
|
+
"\t": "\u21B9",
|
|
60
|
+
// Nonbreaking space as open box
|
|
61
|
+
"\u00A0": "\u237D",
|
|
62
|
+
// Soft hyphen as letter shelf
|
|
63
|
+
"\u00AD": "\u2423",
|
|
64
|
+
|
|
65
|
+
// Other special unicode spaces shown as a space symbol (s/p)
|
|
66
|
+
// See here for details: http://www.cs.tut.fi/~jkorpela/chars/spaces.html
|
|
67
|
+
|
|
68
|
+
// no-break space
|
|
69
|
+
"\u00A0": "\u2420",
|
|
70
|
+
// mongolian vowel separator
|
|
71
|
+
"\u180E": "\u2420",
|
|
72
|
+
// en quad
|
|
73
|
+
"\u2000": "\u2420",
|
|
74
|
+
// em quad
|
|
75
|
+
"\u2001": "\u2420",
|
|
76
|
+
// en space
|
|
77
|
+
"\u2002": "\u2420",
|
|
78
|
+
// em space
|
|
79
|
+
"\u2003": "\u2420",
|
|
80
|
+
// three-per-em space
|
|
81
|
+
"\u2004": "\u2420",
|
|
82
|
+
// four-per-em space
|
|
83
|
+
"\u2005": "\u2420",
|
|
84
|
+
// six-per-em space
|
|
85
|
+
"\u2006": "\u2420",
|
|
86
|
+
// figure space
|
|
87
|
+
"\u2007": "\u2420",
|
|
88
|
+
// punctuation space
|
|
89
|
+
"\u2008": "\u2420",
|
|
90
|
+
// thin space
|
|
91
|
+
"\u2009": "\u2420",
|
|
92
|
+
// hair space
|
|
93
|
+
"\u200A": "\u2420",
|
|
94
|
+
// zero width space
|
|
95
|
+
"\u200B": "\u2420",
|
|
96
|
+
// narrow no-break space
|
|
97
|
+
"\u202F": "\u2420",
|
|
98
|
+
// medium mathematical space
|
|
99
|
+
"\u205F": "\u2420",
|
|
100
|
+
// ideographic space
|
|
101
|
+
"\u3000": "\u2420",
|
|
102
|
+
// zero width no-break space
|
|
103
|
+
"\uFEFF": "\u2420"
|
|
104
|
+
},
|
|
105
|
+
// Whether to visually show white space chars
|
|
106
|
+
"show_white_space": false,
|
|
107
|
+
// Show tab indicators in whitespace
|
|
108
|
+
"show_tab_indicators": true,
|
|
109
|
+
// Tab indicator charatrer
|
|
110
|
+
"tab_indicator_character": "\u203A",
|
|
111
|
+
// Highlight current line(s)
|
|
112
|
+
"highlight_current_line": true,
|
|
113
|
+
// Line numbering
|
|
114
|
+
"show_line_nums": true,
|
|
115
|
+
// Pad line numbers with spaces instead of zeros
|
|
116
|
+
"line_nums_pad_space": true,
|
|
117
|
+
// Color of the line numbers as a terminal color index (0-255).
|
|
118
|
+
// The grayscale ramp runs from 232 (near black) to 255 (near white),
|
|
119
|
+
// so raise this to make line numbers brighter, lower it to dim them.
|
|
120
|
+
// Only used on terminals that support 256 colors.
|
|
121
|
+
"line_number_color": 245,
|
|
122
|
+
// Naive line highlighting
|
|
123
|
+
"show_line_colors": true,
|
|
124
|
+
// Proper syntax highlighting
|
|
125
|
+
"show_highlighting": true,
|
|
126
|
+
// Syntax highlighting theme
|
|
127
|
+
"theme": "monokai",
|
|
128
|
+
// Listen for mouse events
|
|
129
|
+
"use_mouse": false,
|
|
130
|
+
// Whether to use copy/paste across multiple files
|
|
131
|
+
"use_global_buffer": true,
|
|
132
|
+
// Find with regex by default
|
|
133
|
+
"regex_find": false
|
|
134
|
+
},
|
|
135
|
+
// UI Display Settings
|
|
136
|
+
"display": {
|
|
137
|
+
// Show top status bar
|
|
138
|
+
"show_top_bar": true,
|
|
139
|
+
// Show app name and version in top bar
|
|
140
|
+
"show_app_name": true,
|
|
141
|
+
// Show list of open files in top bar
|
|
142
|
+
"show_file_list": true,
|
|
143
|
+
// Show indicator in the file list for files that are modified
|
|
144
|
+
// NOTE: if you experience performance issues, set this to false
|
|
145
|
+
"show_file_modified_indicator": true,
|
|
146
|
+
// Show the keyboard legend
|
|
147
|
+
"show_legend": true,
|
|
148
|
+
// Show the bottom status bar
|
|
149
|
+
"show_bottom_bar": true,
|
|
150
|
+
// Invert status bar colors (switch text and background colors)
|
|
151
|
+
"invert_status_bars": false
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Suplemon Default Key Map
|
|
2
|
+
|
|
3
|
+
// This file contains the default key map for Suplemon and should not be edited.
|
|
4
|
+
// If the file doesn't exist, or if it has errors Suplemon can't run.
|
|
5
|
+
// Suplemon supports single line comments in JSON as seen here.
|
|
6
|
+
|
|
7
|
+
[
|
|
8
|
+
// App
|
|
9
|
+
{"keys": ["f1", "ctrl+h"], "command": "help"},
|
|
10
|
+
{"keys": ["ctrl+s"], "command": "save_file"},
|
|
11
|
+
{"keys": ["ctrl+e"], "command": "run_command"},
|
|
12
|
+
{"keys": ["ctrl+f"], "command": "find"},
|
|
13
|
+
{"keys": ["ctrl+g"], "command": "go_to"},
|
|
14
|
+
{"keys": ["ctrl+o"], "command": "open"},
|
|
15
|
+
{"keys": ["ctrl+w"], "command": "close_file"},
|
|
16
|
+
{"keys": ["ctrl+n"], "command": "new_file"},
|
|
17
|
+
{"keys": ["ctrl+q"], "command": "ask_exit"},
|
|
18
|
+
{"keys": ["ctrl+p"], "command": "comment"},
|
|
19
|
+
{"keys": ["f4", "ctrl+pageup", "ctrl+alt+pageup"], "command": "next_file"},
|
|
20
|
+
{"keys": ["shift+f4", "ctrl+pagedown", "ctrl+alt+pagedown"], "command": "prev_file"},
|
|
21
|
+
{"keys": ["f3"], "command": "save_file_as"},
|
|
22
|
+
{"keys": ["f2"], "command": "reload_file"},
|
|
23
|
+
{"keys": ["f7"], "command": "toggle_whitespace"},
|
|
24
|
+
{"keys": ["f8"], "command": "toggle_mouse"},
|
|
25
|
+
{"keys": ["f12"], "command": "toggle_fullscreen"},
|
|
26
|
+
// Editor
|
|
27
|
+
{"keys": ["up"], "command": "arrow_up"},
|
|
28
|
+
{"keys": ["down"], "command": "arrow_down"},
|
|
29
|
+
{"keys": ["left"], "command": "arrow_left"},
|
|
30
|
+
{"keys": ["right"], "command": "arrow_right"},
|
|
31
|
+
{"keys": ["enter"], "command": "enter"},
|
|
32
|
+
{"keys": ["backspace"], "command": "backspace"},
|
|
33
|
+
{"keys": ["delete"], "command": "delete"},
|
|
34
|
+
{"keys": ["tab"], "command": "tab"},
|
|
35
|
+
{"keys": ["shift+tab"], "command": "untab"},
|
|
36
|
+
{"keys": ["home"], "command": "home"},
|
|
37
|
+
{"keys": ["end"], "command": "end"},
|
|
38
|
+
{"keys": ["escape"], "command": "escape"},
|
|
39
|
+
{"keys": ["pageup"], "command": "page_up"},
|
|
40
|
+
{"keys": ["pagedown"], "command": "page_down"},
|
|
41
|
+
{"keys": ["f5", "ctrl+z"], "command": "undo"},
|
|
42
|
+
{"keys": ["f6", "ctrl+y"], "command": "redo"},
|
|
43
|
+
{"keys": ["f9"], "command": "toggle_line_nums"},
|
|
44
|
+
{"keys": ["f10"], "command": "toggle_line_ends"},
|
|
45
|
+
{"keys": ["f11"], "command": "toggle_highlight"},
|
|
46
|
+
{"keys": ["alt+up"], "command": "new_cursor_up"},
|
|
47
|
+
{"keys": ["alt+down"], "command": "new_cursor_down"},
|
|
48
|
+
{"keys": ["alt+left"], "command": "new_cursor_left"},
|
|
49
|
+
{"keys": ["alt+right"], "command": "new_cursor_right"},
|
|
50
|
+
{"keys": ["alt+pageup"], "command": "push_up"},
|
|
51
|
+
{"keys": ["alt+pagedown"], "command": "push_down"},
|
|
52
|
+
{"keys": ["ctrl+c"], "command": "copy"},
|
|
53
|
+
{"keys": ["ctrl+x"], "command": "cut"},
|
|
54
|
+
{"keys": ["ctrl+k"], "command": "duplicate_line"},
|
|
55
|
+
{"keys": ["ctrl+v", "insert"], "command": "insert"},
|
|
56
|
+
{"keys": ["ctrl+d"], "command": "find_next"},
|
|
57
|
+
{"keys": ["ctrl+a"], "command": "find_all"},
|
|
58
|
+
{"keys": ["ctrl+left"], "command": "jump_left"},
|
|
59
|
+
{"keys": ["ctrl+right"], "command": "jump_right"},
|
|
60
|
+
{"keys": ["ctrl+up"], "command": "jump_up"},
|
|
61
|
+
{"keys": ["ctrl+down"], "command": "jump_down"},
|
|
62
|
+
{"keys": ["ctrl+t"], "command": "strip"}
|
|
63
|
+
]
|
suplemon/config.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# -*- encoding: utf-8
|
|
2
|
+
"""
|
|
3
|
+
Config handler.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from . import suplemon_module
|
|
11
|
+
|
|
12
|
+
# Preamble written to the user config created on first run. The defaults file
|
|
13
|
+
# has its own preamble saying it shouldn't be edited, which is the opposite of
|
|
14
|
+
# what's wanted here, so it's replaced with this one.
|
|
15
|
+
USER_CONFIG_HEADER = """\
|
|
16
|
+
// Suplemon Config
|
|
17
|
+
//
|
|
18
|
+
// Created from Suplemon's defaults on first run, and safe to edit. Any
|
|
19
|
+
// setting you change here overrides the default; anything you delete falls
|
|
20
|
+
// back to it. Single line comments are supported, as seen here.
|
|
21
|
+
//
|
|
22
|
+
// There are three main groups for settings:
|
|
23
|
+
// - app: Global settings
|
|
24
|
+
// - editor: Editor behaviour
|
|
25
|
+
// - display: How the UI looks
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Config:
|
|
31
|
+
def __init__(self, app):
|
|
32
|
+
self.app = app
|
|
33
|
+
self.logger = logging.getLogger(__name__)
|
|
34
|
+
self.default_config_filename = "defaults.json"
|
|
35
|
+
self.default_keymap_filename = "keymap.json"
|
|
36
|
+
self.config_filename = "suplemon-config.json"
|
|
37
|
+
self.keymap_filename = "suplemon-keymap.json"
|
|
38
|
+
self.home_dir = os.path.expanduser("~")
|
|
39
|
+
self.config_dir = os.path.join(self.home_dir, ".config", "suplemon")
|
|
40
|
+
|
|
41
|
+
self.defaults = {}
|
|
42
|
+
self.keymap = {}
|
|
43
|
+
self.config = {}
|
|
44
|
+
self.key_bindings = {}
|
|
45
|
+
|
|
46
|
+
def init(self):
|
|
47
|
+
self.create_config_dir()
|
|
48
|
+
return self.load_defaults()
|
|
49
|
+
|
|
50
|
+
def path(self):
|
|
51
|
+
return os.path.join(self.config_dir, self.config_filename)
|
|
52
|
+
|
|
53
|
+
def keymap_path(self):
|
|
54
|
+
return os.path.join(self.config_dir, self.keymap_filename)
|
|
55
|
+
|
|
56
|
+
def set_path(self, path):
|
|
57
|
+
parts = os.path.split(path)
|
|
58
|
+
self.config_dir = parts[0]
|
|
59
|
+
self.config_filename = parts[1]
|
|
60
|
+
|
|
61
|
+
def load(self):
|
|
62
|
+
path = self.path()
|
|
63
|
+
config = False
|
|
64
|
+
if not os.path.exists(path):
|
|
65
|
+
self.logger.debug("Configuration file '{0}' doesn't exist.".format(path))
|
|
66
|
+
# First run. Give the user a documented config they can actually
|
|
67
|
+
# edit rather than running on defaults they can't see.
|
|
68
|
+
self.create_user_config()
|
|
69
|
+
if os.path.exists(path):
|
|
70
|
+
config = self.load_config_file(path)
|
|
71
|
+
if config is not False:
|
|
72
|
+
self.logger.debug("Loaded configuration file '{0}'".format(path))
|
|
73
|
+
self.config = self.merge_defaults(config)
|
|
74
|
+
else:
|
|
75
|
+
self.logger.info("Failed to load config file '{0}'.".format(path))
|
|
76
|
+
self.config = dict(self.defaults)
|
|
77
|
+
self.load_keys()
|
|
78
|
+
return config
|
|
79
|
+
|
|
80
|
+
def load_keys(self):
|
|
81
|
+
path = self.keymap_path()
|
|
82
|
+
keymap = []
|
|
83
|
+
|
|
84
|
+
# Reload the defaults first. self.keymap already holds the previous
|
|
85
|
+
# merge of defaults and user keymap, so appending to it again would
|
|
86
|
+
# keep bindings the user has since deleted from their keymap file.
|
|
87
|
+
self.load_default_keys()
|
|
88
|
+
|
|
89
|
+
if not os.path.exists(path):
|
|
90
|
+
self.logger.debug("Keymap file '{0}' doesn't exist.".format(path))
|
|
91
|
+
else:
|
|
92
|
+
keymap = self.load_config_file(path) or []
|
|
93
|
+
if not keymap:
|
|
94
|
+
self.logger.warning("Failed to load keymap file '{0}'.".format(path))
|
|
95
|
+
|
|
96
|
+
# Build the key bindings
|
|
97
|
+
# User keymap overwrites the defaults in the bindings
|
|
98
|
+
self.keymap = self.normalize_keys(self.keymap + keymap)
|
|
99
|
+
self.key_bindings = {}
|
|
100
|
+
for binding in self.keymap:
|
|
101
|
+
for key in binding["keys"]:
|
|
102
|
+
self.key_bindings[key] = binding["command"]
|
|
103
|
+
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
def normalize_keys(self, keymap):
|
|
107
|
+
"""Normalize the order of modifier keys in keymap."""
|
|
108
|
+
modifiers = ["shift", "ctrl", "alt", "meta"] # The modifiers in correct order
|
|
109
|
+
for item in keymap:
|
|
110
|
+
new_keys = []
|
|
111
|
+
for key_item in item["keys"]:
|
|
112
|
+
parts = key_item.split("+")
|
|
113
|
+
key = parts[-1]
|
|
114
|
+
if len(parts) < 2:
|
|
115
|
+
new_keys.append(key)
|
|
116
|
+
continue
|
|
117
|
+
normalized = ""
|
|
118
|
+
for mod in modifiers: # Add the used modifiers back in correct order
|
|
119
|
+
if mod in parts:
|
|
120
|
+
normalized += mod + "+"
|
|
121
|
+
normalized += key
|
|
122
|
+
new_keys.append(normalized)
|
|
123
|
+
item["keys"] = new_keys
|
|
124
|
+
return keymap
|
|
125
|
+
|
|
126
|
+
def load_defaults(self):
|
|
127
|
+
if not self.load_default_config() or not self.load_default_keys():
|
|
128
|
+
return False
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
def default_config_path(self):
|
|
132
|
+
"""Path of the default config file shipped with Suplemon."""
|
|
133
|
+
return os.path.join(self.app.path, "config", self.default_config_filename)
|
|
134
|
+
|
|
135
|
+
def create_user_config(self):
|
|
136
|
+
"""Create the user config file by copying the defaults.
|
|
137
|
+
|
|
138
|
+
Run on first start so that the user gets a commented config file to
|
|
139
|
+
edit instead of silently running on built in defaults.
|
|
140
|
+
|
|
141
|
+
:return: True if the config file was created.
|
|
142
|
+
"""
|
|
143
|
+
source = self.default_config_path()
|
|
144
|
+
target = self.path()
|
|
145
|
+
try:
|
|
146
|
+
with open(source) as f:
|
|
147
|
+
data = f.read()
|
|
148
|
+
except OSError:
|
|
149
|
+
self.logger.warning("Couldn't read default config '{0}'.".format(source))
|
|
150
|
+
return False
|
|
151
|
+
try:
|
|
152
|
+
with open(target, "w") as f:
|
|
153
|
+
f.write(self.build_user_config(data))
|
|
154
|
+
except OSError:
|
|
155
|
+
self.logger.warning("Couldn't create config file '{0}'.".format(target))
|
|
156
|
+
return False
|
|
157
|
+
self.logger.info("Created config file '{0}' from the defaults.".format(target))
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def build_user_config(self, data):
|
|
161
|
+
"""Replace the default config's preamble with a user facing one.
|
|
162
|
+
|
|
163
|
+
Everything before the opening brace of the defaults is a comment block
|
|
164
|
+
stating the file shouldn't be edited. The settings themselves and the
|
|
165
|
+
comments documenting them are left untouched.
|
|
166
|
+
|
|
167
|
+
:param data: Contents of the default config file.
|
|
168
|
+
:return: Contents to write to the user config file.
|
|
169
|
+
"""
|
|
170
|
+
lines = data.split("\n")
|
|
171
|
+
for i, line in enumerate(lines):
|
|
172
|
+
if line.lstrip().startswith("{"):
|
|
173
|
+
return USER_CONFIG_HEADER + "\n".join(lines[i:])
|
|
174
|
+
# Nothing that looks like a config body, copy it verbatim
|
|
175
|
+
return data
|
|
176
|
+
|
|
177
|
+
def load_default_config(self):
|
|
178
|
+
path = self.default_config_path()
|
|
179
|
+
config = self.load_config_file(path)
|
|
180
|
+
if not config:
|
|
181
|
+
self.logger.error("Failed to load default config file '{0}'!".format(path))
|
|
182
|
+
return False
|
|
183
|
+
self.defaults = config
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
def load_module_configs(self):
|
|
187
|
+
module_config = {}
|
|
188
|
+
modules = self.app.modules.modules
|
|
189
|
+
for module_name in modules.keys():
|
|
190
|
+
module = modules[module_name]
|
|
191
|
+
conf = module.get_default_config()
|
|
192
|
+
module_config[module_name] = conf
|
|
193
|
+
self.logger.debug("Loading default config for module '%s': %s" % (module_name, str(conf)))
|
|
194
|
+
self.defaults["modules"] = module_config
|
|
195
|
+
self.config = self.merge_defaults(self.config)
|
|
196
|
+
|
|
197
|
+
def load_default_keys(self):
|
|
198
|
+
path = os.path.join(self.app.path, "config", self.default_keymap_filename)
|
|
199
|
+
config = self.load_config_file(path)
|
|
200
|
+
if not config:
|
|
201
|
+
self.logger.error("Failed to load default keymap file '{0}'!".format(path))
|
|
202
|
+
return False
|
|
203
|
+
self.keymap = config
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def reload(self):
|
|
207
|
+
"""Reload the config file."""
|
|
208
|
+
return self.load()
|
|
209
|
+
|
|
210
|
+
def store(self):
|
|
211
|
+
"""Write current config state to file.
|
|
212
|
+
|
|
213
|
+
:return: True if the config was written.
|
|
214
|
+
"""
|
|
215
|
+
data = json.dumps(self.config, indent=4)
|
|
216
|
+
try:
|
|
217
|
+
with open(self.path(), "w", encoding="utf-8") as f:
|
|
218
|
+
f.write(data)
|
|
219
|
+
except OSError:
|
|
220
|
+
self.logger.warning("Couldn't write config file '{0}'.".format(self.path()))
|
|
221
|
+
return False
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
def merge_defaults(self, config):
|
|
225
|
+
"""Fill any missing config options with defaults."""
|
|
226
|
+
return self._merge_defaults(self.defaults, config)
|
|
227
|
+
|
|
228
|
+
def _merge_defaults(self, defaults, config):
|
|
229
|
+
"""Recursivley merge two dicts."""
|
|
230
|
+
for key in defaults.keys():
|
|
231
|
+
item = defaults[key]
|
|
232
|
+
if key not in config.keys():
|
|
233
|
+
config[key] = item
|
|
234
|
+
continue
|
|
235
|
+
if not isinstance(item, dict):
|
|
236
|
+
continue
|
|
237
|
+
config[key] = self._merge_defaults(item, config[key])
|
|
238
|
+
return config
|
|
239
|
+
|
|
240
|
+
def load_config_file(self, path):
|
|
241
|
+
try:
|
|
242
|
+
f = open(path)
|
|
243
|
+
data = f.read()
|
|
244
|
+
f.close()
|
|
245
|
+
data = self.remove_config_comments(data)
|
|
246
|
+
config = json.loads(data)
|
|
247
|
+
return config
|
|
248
|
+
except (OSError, ValueError):
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def remove_config_comments(self, data):
|
|
252
|
+
"""Remove comments from a 'pseudo' JSON config file.
|
|
253
|
+
|
|
254
|
+
Removes all lines that begin with '#' or '//' ignoring whitespace.
|
|
255
|
+
|
|
256
|
+
:param data: Commented JSON data to clean.
|
|
257
|
+
:return: Cleaned pure JSON.
|
|
258
|
+
"""
|
|
259
|
+
lines = data.split("\n")
|
|
260
|
+
cleaned = []
|
|
261
|
+
for line in lines:
|
|
262
|
+
line = line.strip()
|
|
263
|
+
if line.startswith(("//", "#")):
|
|
264
|
+
continue
|
|
265
|
+
cleaned.append(line)
|
|
266
|
+
return "\n".join(cleaned)
|
|
267
|
+
|
|
268
|
+
def create_config_dir(self):
|
|
269
|
+
if not os.path.exists(self.config_dir):
|
|
270
|
+
try:
|
|
271
|
+
os.makedirs(self.config_dir)
|
|
272
|
+
except OSError:
|
|
273
|
+
self.app.logger.warning("Config folder '{0}' doesn't exist and couldn't be created.".format(
|
|
274
|
+
self.config_dir))
|
|
275
|
+
|
|
276
|
+
def __getitem__(self, i):
|
|
277
|
+
"""Get a config variable."""
|
|
278
|
+
return self.config[i]
|
|
279
|
+
|
|
280
|
+
def __setitem__(self, i, v):
|
|
281
|
+
"""Set a config variable."""
|
|
282
|
+
self.config[i] = v
|
|
283
|
+
|
|
284
|
+
def __str__(self):
|
|
285
|
+
"""Convert entire config array to string."""
|
|
286
|
+
return str(self.config)
|
|
287
|
+
|
|
288
|
+
def __len__(self):
|
|
289
|
+
"""Return length of top level config variables."""
|
|
290
|
+
return len(self.config)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ConfigModule(suplemon_module.Module):
|
|
294
|
+
"""Helper for shortcut for opening config files."""
|
|
295
|
+
def init(self):
|
|
296
|
+
self.config_name = "defaults.json"
|
|
297
|
+
self.config_default_path = os.path.join(self.app.path, "config", self.config_name)
|
|
298
|
+
self.config_user_path = self.app.config.path()
|
|
299
|
+
|
|
300
|
+
def run(self, app, editor, args):
|
|
301
|
+
if args == "defaults":
|
|
302
|
+
# Open the default config in a new file only for viewing
|
|
303
|
+
self.open(app, self.config_default_path, read_only=True)
|
|
304
|
+
else:
|
|
305
|
+
self.open(app, self.config_user_path)
|
|
306
|
+
|
|
307
|
+
def open(self, app, path, read_only=False):
|
|
308
|
+
if read_only:
|
|
309
|
+
f = open(path)
|
|
310
|
+
data = f.read()
|
|
311
|
+
f.close()
|
|
312
|
+
file = app.new_file()
|
|
313
|
+
file.set_name(self.config_name)
|
|
314
|
+
file.set_data(data)
|
|
315
|
+
app.switch_to_file(app.last_file_index())
|
|
316
|
+
else:
|
|
317
|
+
# Open the user config file for editing
|
|
318
|
+
f = app.file_is_open(path)
|
|
319
|
+
if f:
|
|
320
|
+
app.switch_to_file(app.get_file_index(f))
|
|
321
|
+
else:
|
|
322
|
+
if not app.open_file(path):
|
|
323
|
+
app.new_file(path)
|
|
324
|
+
app.switch_to_file(app.last_file_index())
|