diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5836de9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+
+# 4 space indentation
+[*.py]
+indent_style = space
+indent_size = 4
diff --git a/setup.py b/setup.py
index ef9cba1..00d4396 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
from setuptools import setup
version = re.search(
- '^__version__\s*=\s*"([^"]*)"',
+ r'^__version__\s*=\s*"([^"]*)"',
open("suplemon/main.py").read(),
re.M
).group(1)
diff --git a/suplemon/cli.py b/suplemon/cli.py
index 00171a5..78a0663 100755
--- a/suplemon/cli.py
+++ b/suplemon/cli.py
@@ -19,20 +19,23 @@ def main():
"""Handle CLI invocation"""
# Parse our CLI arguments
config_file = None
+ log_level = None
if argparse:
parser = argparse.ArgumentParser(description="Console text editor with multi cursor support")
- parser.add_argument("filenames", metavar="filename", type=str, nargs="*", help="Files to load into Suplemon")
+ parser.add_argument("filenames", metavar="filename", type=str, nargs="*", help="files to open")
parser.add_argument("--version", action="version", version=__version__)
- parser.add_argument("--config", type=str, help="Configuration file path.")
+ parser.add_argument("--config", type=str, help="configuration file path")
+ parser.add_argument("--log-level", type=int, help="debug logging level")
args = parser.parse_args()
filenames = args.filenames
config_file = args.config
+ log_level = args.log_level
else:
# Python < 2.7 fallback
filenames = sys.argv[1:]
# Generate and start our application
- app = App(filenames=filenames, config_file=config_file)
+ app = App(filenames=filenames, config_file=config_file, log_level=log_level)
if app.init():
app.run()
diff --git a/suplemon/config.py b/suplemon/config.py
index ba7f7dc..ffb85b1 100644
--- a/suplemon/config.py
+++ b/suplemon/config.py
@@ -64,17 +64,18 @@ def load_keys(self):
if not os.path.exists(path):
self.logger.debug("Keymap file '{0}' doesn't exist.".format(path))
else:
- keymap = self.load_config_file(path) or []
- if not keymap:
+ loaded_keymap = self.load_config_file(path)
+ if loaded_keymap is False:
self.logger.warning("Failed to load keymap file '{0}'.".format(path))
-
+ else:
+ keymap = loaded_keymap
# Build the key bindings
# User keymap overwrites the defaults in the bindings
self.keymap = self.normalize_keys(self.keymap + keymap)
self.key_bindings = {}
for binding in self.keymap:
for key in binding["keys"]:
- self.key_bindings[key] = binding["command"]
+ self.key_bindings[key] = binding
return True
diff --git a/suplemon/config/defaults.json b/suplemon/config/defaults.json
index 5fcc532..bcc057f 100644
--- a/suplemon/config/defaults.json
+++ b/suplemon/config/defaults.json
@@ -46,7 +46,7 @@
"punctuation": " (){}[]<>$@!%'\"=+-/*.:,;_\n\r",
// Character to use to visualize end of line
"line_end_char": "",
- // White space characters and their visual matches
+ // White space characters and their visual representations
"white_space_map": {
// Null byte as null symbol
"\u0000": "\u2400",
@@ -100,7 +100,7 @@
"\uFEFF": "\u2420"
},
// Whether to visually show white space chars
- "show_white_space": false,
+ "show_white_space": true,
// Show tab indicators in whitespace
"show_tab_indicators": true,
// Tab indicator charatrer
diff --git a/suplemon/config/keymap.json b/suplemon/config/keymap.json
index f6e541a..5224146 100644
--- a/suplemon/config/keymap.json
+++ b/suplemon/config/keymap.json
@@ -7,17 +7,17 @@
[
// App
{"keys": ["ctrl+h"], "command": "help"},
- {"keys": ["ctrl+s"], "command": "save_file"},
+ {"keys": ["ctrl+s"], "command": "save"},
{"keys": ["ctrl+e"], "command": "run_command"},
{"keys": ["ctrl+f"], "command": "find"},
{"keys": ["ctrl+g"], "command": "go_to"},
- {"keys": ["ctrl+o"], "command": "open"},
- {"keys": ["ctrl+w"], "command": "close_file"},
+ {"keys": ["ctrl+o"], "command": "prompt_open_file"},
+ {"keys": ["ctrl+w"], "command": "close"},
{"keys": ["ctrl+n"], "command": "new_file"},
- {"keys": ["ctrl+q"], "command": "ask_exit"},
+ {"keys": ["ctrl+q"], "command": "exit"},
{"keys": ["ctrl+p"], "command": "comment"},
- {"keys": ["ctrl+pageup"], "command": "next_file"},
- {"keys": ["ctrl+pagedown"], "command": "prev_file"},
+ {"keys": ["shift+pagedown"], "command": "next_view"},
+ {"keys": ["shift+pageup"], "command": "prev_view"},
{"keys": ["f1"], "command": "save_file_as"},
{"keys": ["f2"], "command": "reload_file"},
{"keys": ["f7"], "command": "toggle_whitespace"},
@@ -43,12 +43,12 @@
{"keys": ["f9"], "command": "toggle_line_nums"},
{"keys": ["f10"], "command": "toggle_line_ends"},
{"keys": ["f11"], "command": "toggle_highlight"},
- {"keys": ["alt+up"], "command": "new_cursor_up"},
- {"keys": ["alt+down"], "command": "new_cursor_down"},
+ {"keys": ["alt+shift+up"], "command": "new_cursor_up"},
+ {"keys": ["alt+shift+down"], "command": "new_cursor_down"},
{"keys": ["alt+left"], "command": "new_cursor_left"},
{"keys": ["alt+right"], "command": "new_cursor_right"},
- {"keys": ["alt+pageup"], "command": "push_up"},
- {"keys": ["alt+pagedown"], "command": "push_down"},
+ {"keys": ["ctrl+shift+up"], "command": "swap_line_up"},
+ {"keys": ["ctrl+shift+down"], "command": "swap_line_down"},
{"keys": ["ctrl+c"], "command": "copy"},
{"keys": ["ctrl+x"], "command": "cut"},
{"keys": ["ctrl+k"], "command": "duplicate_line"},
@@ -57,7 +57,9 @@
{"keys": ["ctrl+a"], "command": "find_all"},
{"keys": ["ctrl+left"], "command": "jump_left"},
{"keys": ["ctrl+right"], "command": "jump_right"},
- {"keys": ["ctrl+up"], "command": "jump_up"},
- {"keys": ["ctrl+down"], "command": "jump_down"},
+ {"keys": ["alt+up"], "command": "jump_up"},
+ {"keys": ["alt+down"], "command": "jump_down"},
+ {"keys": ["ctrl+up"], "command": "scroll_lines", "args": {"amount": 1}},
+ {"keys": ["ctrl+down"], "command": "scroll_lines", "args": {"amount": -1}},
{"keys": ["ctrl+t"], "command": "strip"}
-]
\ No newline at end of file
+]
diff --git a/suplemon/editor.py b/suplemon/editor.py
index 4a1d813..ade6944 100644
--- a/suplemon/editor.py
+++ b/suplemon/editor.py
@@ -60,31 +60,31 @@ def __init__(self, app, window):
def init(self):
Viewer.init(self)
operations = {
- "backspace": self.backspace, # Backspace
- "delete": self.delete, # Delete
- "insert": self.insert, # Insert
- "enter": self.enter, # Enter
- "tab": self.tab, # Tab
- "untab": self.untab, # Shift + Tab
- "escape": self.escape, # Escape
- "single_selection": self.single_selection, # Escape
- "clear_last_find": self.clear_last_find, # Escape
- "new_cursor_up": self.new_cursor_up, # Alt + Up
- "new_cursor_down": self.new_cursor_down, # Alt + Down
- "new_cursor_left": self.new_cursor_left, # Alt + Left
- "new_cursor_right": self.new_cursor_right, # Alt + Right
- "page_up": self.page_up, # Page Up
- "page_down": self.page_down, # Page Down
- "push_up": self.push_up, # Alt + Page Up
- "push_down": self.push_down, # Alt + Page Down
- "undo": self.undo, # F5
- "redo": self.redo, # F6
- "toggle_line_nums": self.toggle_line_nums, # F9
- "toggle_line_ends": self.toggle_line_ends, # F10
- "toggle_highlight": self.toggle_highlight, # F11
- "copy": self.copy, # Ctrl + C
- "cut": self.cut, # Ctrl + X
- "duplicate_line": self.duplicate_line, # Ctrl + W
+ "backspace": self.backspace,
+ "delete": self.delete,
+ "insert": self.insert,
+ "enter": self.enter,
+ "tab": self.tab,
+ "untab": self.untab,
+ "escape": self.escape,
+ "single_selection": self.single_selection,
+ "clear_last_find": self.clear_last_find,
+ "new_cursor_up": self.new_cursor_up,
+ "new_cursor_down": self.new_cursor_down,
+ "new_cursor_left": self.new_cursor_left,
+ "new_cursor_right": self.new_cursor_right,
+ "page_up": self.page_up,
+ "page_down": self.page_down,
+ "swap_line_up": self.swap_line_up,
+ "swap_line_down": self.swap_line_down,
+ "undo": self.undo,
+ "redo": self.redo,
+ "toggle_line_nums": self.toggle_line_nums,
+ "toggle_line_ends": self.toggle_line_ends,
+ "toggle_highlight": self.toggle_highlight,
+ "copy": self.copy,
+ "cut": self.cut,
+ "duplicate_line": self.duplicate_line,
}
for key in operations.keys():
self.operations[key] = operations[key]
@@ -411,7 +411,7 @@ def insert_lines_at(self, lines, at):
self.lines.insert(at, Line(line))
self.move_y_cursors(at, len(lines))
- def push_up(self):
+ def swap_line_up(self):
"""Move current lines up by one line."""
used_y = []
curs = sorted(self.cursors, key=lambda c: (c[1], c[0]))
@@ -426,10 +426,10 @@ def push_up(self):
self.lines[cursor.y] = old
self.move_cursors((0, -1))
self.scroll_up()
- # Add a restore point if previous action != push_up
- self.store_action_state("push_up")
+ # Add a restore point if previous action != swap_line_up
+ self.store_action_state("swap_line_up")
- def push_down(self):
+ def swap_line_down(self):
"""Move current lines down by one line."""
used_y = []
curs = reversed(sorted(self.cursors, key=lambda c: (c[1], c[0])))
@@ -445,8 +445,8 @@ def push_down(self):
self.move_cursors((0, 1))
self.scroll_down()
- # Add a restore point if previous action != push_down
- self.store_action_state("push_down")
+ # Add a restore point if previous action != swap_line_down
+ self.store_action_state("swap_line_down")
def tab(self):
"""Indent lines."""
diff --git a/suplemon/file.py b/suplemon/file.py
index 9aec08a..43dc4f0 100644
--- a/suplemon/file.py
+++ b/suplemon/file.py
@@ -94,7 +94,7 @@ def save(self):
f = open(self._path(), "w")
f.write(data)
f.close()
- except:
+ except: # noqa: E722 (unrecoverable)
return False
self.data = data
self.last_save = time.time()
@@ -134,7 +134,7 @@ def _read_text(self, file):
data = f.read()
f.close()
return data
- except:
+ except: # noqa: E722 (unrecoverable)
self.logger.exception("Failed reading file \"{file}\"".format(file=file))
return False
@@ -152,7 +152,7 @@ def _read_binary(self, file):
return False
self.logger.info("Trying to decode with encoding '{0}'".format(charenc))
return data.decode(charenc)
- except:
+ except: # noqa: E722 (unrecoverable)
self.logger.warning("Failed reading binary file!", exc_info=True)
return False
diff --git a/suplemon/key_mappings.py b/suplemon/key_mappings.py
index caa79c8..4acea32 100644
--- a/suplemon/key_mappings.py
+++ b/suplemon/key_mappings.py
@@ -100,10 +100,6 @@
"^Y": "ctrl+y",
"^Z": "ctrl+z", # Conflicts with suspend
- 544: "ctrl+left",
- 559: "ctrl+right",
- 565: "ctrl+up",
- 524: "ctrl+down",
"kLFT5": "ctrl+left",
"kRIT5": "ctrl+right",
"kUP5": "ctrl+up",
@@ -124,6 +120,10 @@
"O5R": "ctrl+f3",
"O5S": "ctrl+f4",
+ "KEY_F(25)": "ctrl+f1",
+ "KEY_F(26)": "ctrl+f2",
+ "KEY_F(27)": "ctrl+f3",
+ "KEY_F(28)": "ctrl+f4",
"KEY_F(29)": "ctrl+f5",
"KEY_F(30)": "ctrl+f6",
"KEY_F(31)": "ctrl+f7",
@@ -153,6 +153,10 @@
"kPRV3": "alt+pageup",
"kNXT3": "alt+pagedown",
+ "KEY_F(49)": "alt+f1",
+ "KEY_F(50)": "alt+f2",
+ "KEY_F(51)": "alt+f3",
+ "KEY_F(52)": "alt+f4",
"KEY_F(53)": "alt+f5",
"KEY_F(54)": "alt+f6",
"KEY_F(55)": "alt+f7",
@@ -185,6 +189,10 @@
"O2R": "shift+f3",
"O2S": "shift+f4",
+ "KEY_F(13)": "shift+f1",
+ "KEY_F(14)": "shift+f2",
+ "KEY_F(15)": "shift+f3",
+ "KEY_F(16)": "shift+f4",
"KEY_F(17)": "shift+f5",
"KEY_F(18)": "shift+f6",
"KEY_F(19)": "shift+f7",
@@ -214,15 +222,28 @@
"kEND4": "shift+alt+end",
- # Control + Shift
- "kUP6": "ctrl+shift+up",
- "kDN6": "ctrl+shift+down",
- "kLFT6": "ctrl+shift+left",
- "kRIT6": "ctrl+shift+right",
-
- "kDC6": "ctrl+shift+delete",
- "kHOM6": "ctrl+shift+home",
- "kEND6": "ctrl+shift+end",
+ # Shift + Control
+ "KEY_F(37)": "shift+ctrl+f1",
+ "KEY_F(38)": "shift+ctrl+f2",
+ "KEY_F(39)": "shift+ctrl+f3",
+ "KEY_F(40)": "shift+ctrl+f4",
+ "KEY_F(41)": "shift+ctrl+f5",
+ "KEY_F(42)": "shift+ctrl+f6",
+ "KEY_F(43)": "shift+ctrl+f7",
+ "KEY_F(44)": "shift+ctrl+f8",
+ "KEY_F(45)": "shift+ctrl+f9",
+ "KEY_F(46)": "shift+ctrl+f10",
+ "KEY_F(47)": "shift+ctrl+f11",
+ "KEY_F(48)": "shift+ctrl+f12",
+
+ "kUP6": "shift+ctrl+up",
+ "kDN6": "shift+ctrl+down",
+ "kLFT6": "shift+ctrl+left",
+ "kRIT6": "shift+ctrl+right",
+
+ "kDC6": "shift+ctrl+delete",
+ "kHOM6": "shift+ctrl+home",
+ "kEND6": "shift+ctrl+end",
# Control + Alt
diff --git a/suplemon/lexer.py b/suplemon/lexer.py
index 94ccd55..7d00ed3 100644
--- a/suplemon/lexer.py
+++ b/suplemon/lexer.py
@@ -1,5 +1,6 @@
# -*- encoding: utf-8
+import logging
import pygments
import pygments.token
import pygments.lexers
@@ -8,23 +9,53 @@
class Lexer:
def __init__(self, app):
self.app = app
+ self.logger = logging.getLogger(__name__)
self.token_map = {
- pygments.token.Comment: "comment",
- pygments.token.Comment.Single: "comment",
- pygments.token.Operator: "keyword",
- pygments.token.Name.Function: "entity.name.function",
- pygments.token.Name.Class: "entity.name.class",
- pygments.token.Name.Tag: "entity.name.tag",
- pygments.token.Name.Attribute: "entity.other.attribute-name",
- pygments.token.Name.Variable: "variable",
- pygments.token.Name.Builtin.Pseudo: "constant.language",
- pygments.token.Literal.String: "string",
- pygments.token.Literal.String.Doc: "string",
- pygments.token.Punctuation: "punctuation",
- pygments.token.Literal.Number: "constant.numeric",
- pygments.token.Name: "entity.name",
- pygments.token.Keyword: "keyword",
- pygments.token.Generic.Deleted: "invalid",
+ pygments.token.Text: "generic",
+ pygments.token.Generic.Strong: "string",
+ pygments.token.Generic.Subheading: "string",
+ pygments.token.Generic.Deleted: "invalid",
+ pygments.token.Punctuation: "punctuation",
+ pygments.token.Operator: "keyword",
+ pygments.token.Operator.Word: "keyword.control",
+
+ pygments.token.Comment: "comment",
+ pygments.token.Comment.Single: "comment",
+ pygments.token.Comment.Multiline: "comment",
+
+ pygments.token.Name: "entity.name",
+ pygments.token.Name.Other: "entity.name",
+ pygments.token.Name.Tag: "entity.name.tag",
+ pygments.token.Name.Class: "entity.name.class",
+ pygments.token.Name.Function: "entity.name.function",
+ pygments.token.Name.Function.Magic: "entity.name.function",
+ pygments.token.Name.Attribute: "entity.other.attribute-name",
+ pygments.token.Name.Variable: "variable",
+ pygments.token.Name.Variable.Magic: "variable",
+ pygments.token.Name.Builtin: "constant.language",
+ pygments.token.Name.Builtin.Pseudo: "constant.language",
+ pygments.token.Name.Namespace: "constant.language",
+ pygments.token.Name.Exception: "entity.name.type",
+
+ pygments.token.Literal.String: "string",
+ pygments.token.Literal.String.Doc: "string",
+ pygments.token.Literal.String.Single: "string",
+ pygments.token.Literal.String.Double: "string",
+ pygments.token.Literal.String.Regex: "string",
+ pygments.token.Literal.String.Backtick: "string",
+ pygments.token.Literal.String.Interpol: "string.interpolated",
+ pygments.token.Literal.Number: "constant.numeric",
+ pygments.token.Literal.Number.Hex: "constant.numeric",
+ pygments.token.Literal.Number.Float: "constant.numeric",
+ pygments.token.Literal.Number.Integer: "constant.numeric",
+
+ pygments.token.Keyword: "keyword",
+ pygments.token.Keyword.Reserved: "constant.language",
+ pygments.token.Keyword.Constant: "constant.language",
+ pygments.token.Keyword.Namespace: "keyword",
+ pygments.token.Keyword.Declaration: "keyword",
+
+ pygments.token.Error: "invalid",
}
def lex(self, code, lex):
diff --git a/suplemon/main.py b/suplemon/main.py
index 452a7f9..2c29e9c 100644
--- a/suplemon/main.py
+++ b/suplemon/main.py
@@ -22,7 +22,7 @@
class App:
- def __init__(self, filenames=None, config_file=None):
+ def __init__(self, filenames=None, config_file=None, log_level=None):
"""
Handle App initialization
@@ -33,6 +33,7 @@ def __init__(self, filenames=None, config_file=None):
self.inited = False
self.running = False
self.debug = True
+ self.log_level = log_level
self.block_rendering = False
# Set default variables
@@ -61,16 +62,15 @@ def __init__(self, filenames=None, config_file=None):
# Define core operations
self.operations = {
"help": self.help,
- "save_file": self.save_file,
+ "save": self.save_file,
"run_command": self.query_command,
"go_to": self.go_to,
- "open": self.open,
- "close_file": self.close_file,
+ "prompt_open_file": self.open,
+ "close": self.close_file,
"new_file": self.new_file,
"exit": self.ask_exit,
- "ask_exit": self.ask_exit,
- "prev_file": self.prev_file,
- "next_file": self.next_file,
+ "prev_view": self.prev_file,
+ "next_view": self.next_file,
"save_file_as": self.save_file_as,
"reload_file": self.reload_file,
"toggle_mouse": self.toggle_mouse,
@@ -99,6 +99,8 @@ def init(self):
# Configure logger
self.debug = self.config["app"]["debug"]
debug_level = self.config["app"]["debug_level"]
+ if self.log_level is not None:
+ debug_level = self.log_level
self.logger.debug("Setting debug_level to {0}.".format(debug_level))
self.logger.setLevel(debug_level)
[handler.setLevel(debug_level) for handler in self.logger.handlers]
@@ -312,17 +314,24 @@ def handle_key(self, event):
"""
key_bindings = self.get_key_bindings()
- operation = None
+ binding = None
+ args = {}
if event.key_name in key_bindings.keys():
- operation = key_bindings[event.key_name]
+ binding = key_bindings[event.key_name]
elif event.key_code in key_bindings.keys():
- operation = key_bindings[event.key_code]
+ binding = key_bindings[event.key_code]
- if operation in self.operations.keys():
- self.run_operation(operation)
+ if not binding:
+ return False
+
+ if "args" in binding:
+ args = binding["args"]
+
+ if binding["command"] in self.operations.keys():
+ self.run_operation(binding["command"], args)
return True
- elif operation in self.modules.modules.keys():
- self.run_module(operation)
+ elif binding["command"] in self.modules.modules.keys():
+ self.run_module(binding["command"], args)
return False
@@ -386,8 +395,8 @@ def new_file(self, path=None):
def ask_exit(self):
"""Exit if no unsaved changes, else make sure the user really wants to exit."""
if self.unsaved_changes():
- yes = self.ui.query_bool("Exit?")
- if yes:
+ confirmed = self.ui.query_bool("You have unsaved changes, exit anyway?")
+ if confirmed:
self.exit()
return True
return False
@@ -494,21 +503,22 @@ def run_module(self, module_name, args=""):
self.logger.exception("Running command failed!")
return False
- def run_operation(self, operation):
+ def run_operation(self, operation, args={}):
"""Run an app core operation."""
- # Support arbitrary callables. TODO: deprecate
- if hasattr(operation, "__call__"):
- return operation()
-
if operation in self.operations.keys():
cancel = self.trigger_event_before(operation)
if not cancel:
- result = self.operations[operation]()
+ try:
+ result = self.operations[operation](**args)
+ except TypeError:
+ self.logger.exception("Invalid key binding arguments for command '{0}'!".format(operation))
+ return False
self.trigger_event_after(operation)
return result
elif operation in self.modules.modules.keys():
cancel = self.trigger_event_before(operation)
if not cancel:
+ # TODO: check how to deal with args for modules
result = self.modules.modules[operation].run(self, self.get_editor(), "")
self.trigger_event_after(operation)
return result
@@ -666,7 +676,12 @@ def save_file_as(self, file=False):
if target_dir and not os.path.exists(target_dir):
if self.ui.query_bool("The path doesn't exist, do you want to create it?"):
self.logger.debug("Creating missing folders in save path.")
- os.makedirs(target_dir)
+ try:
+ os.makedirs(target_dir)
+ except OSError:
+ self.logger.exception("Couldn't create directories!")
+ self.ui.set_status("Couldn't create directories!")
+ return False
else:
return False
f.set_path(full_path)
diff --git a/suplemon/modules/autocomplete.py b/suplemon/modules/autocomplete.py
index d930a7e..ce1b6d8 100644
--- a/suplemon/modules/autocomplete.py
+++ b/suplemon/modules/autocomplete.py
@@ -20,7 +20,7 @@ def init(self):
self.word_list = []
self.bind_event("tab", self.auto_complete)
self.bind_event_after("app_loaded", self.build_word_list)
- self.bind_event_after("save_file", self.build_word_list)
+ self.bind_event_after("save", self.build_word_list)
self.bind_event_after("save_file_as", self.build_word_list)
def get_separators(self):
diff --git a/suplemon/modules/date.py b/suplemon/modules/date.py
index ecc2e4e..7ff0013 100644
--- a/suplemon/modules/date.py
+++ b/suplemon/modules/date.py
@@ -9,7 +9,7 @@ class Date(Module):
"""Shows the current date without year in the top status bar."""
def get_status(self):
- s = time.strftime("%d.%m.")
+ s = time.strftime("%Y-%m-%d")
if self.app.config["app"]["use_unicode_symbols"]:
return "" + s
return s
diff --git a/suplemon/modules/input_test.py b/suplemon/modules/input_test.py
new file mode 100644
index 0000000..9e8980b
--- /dev/null
+++ b/suplemon/modules/input_test.py
@@ -0,0 +1,23 @@
+# -*- encoding: utf-8
+
+from suplemon.suplemon_module import Module
+
+
+class InputTest(Module):
+ """
+ Input event testing utility.
+ """
+
+ def handler(self, prompt, event):
+ self.app.set_status("InputEvent:" + str(event))
+ prompt.on_ready()
+ return True # Disable normal key handling
+
+ def run(self, app, editor, args):
+ app.ui.query_filtered("Waiting for input...", handler=self.handler)
+
+
+module = {
+ "class": InputTest,
+ "name": "input_test",
+}
diff --git a/suplemon/modules/linter.py b/suplemon/modules/linter.py
index 23db441..5997f7c 100644
--- a/suplemon/modules/linter.py
+++ b/suplemon/modules/linter.py
@@ -22,10 +22,10 @@ def init(self):
# Show linting messages in status bar
self.bind_event_after("mainloop", self.mainloop)
# Re-lint current file when appropriate
- self.bind_event_after("save_file", self.lint_current_file)
+ self.bind_event_after("save", self.lint_current_file)
self.bind_event_after("save_file_as", self.lint_current_file)
self.bind_event_after("reload_file", self.lint_current_file)
- self.bind_event_after("open_file", self.lint_current_file)
+ self.bind_event_after("open", self.lint_current_file)
def run(self, app, editor, args):
"""Run the linting command."""
@@ -207,8 +207,8 @@ def get_file_linting(self, path):
parts = line.split(", ")
if len(parts) < 3:
continue
- line_no = int(re.sub("\D", "", parts[0]))
- char_no = int(re.sub("\D", "", parts[1]))
+ line_no = int(re.sub(r"\D", "", parts[0]))
+ char_no = int(re.sub(r"\D", "", parts[1]))
data = parts[2]
err_code = None
if line_no not in linting.keys():
diff --git a/suplemon/themes.py b/suplemon/themes.py
index 23ea166..bfa0dd5 100644
--- a/suplemon/themes.py
+++ b/suplemon/themes.py
@@ -13,6 +13,7 @@
from . import hex2xterm
# Map scope name to its color pair index
+# Numbers don't matter but must be unique
scope_to_pair = {
"global": 21,
"comment": 22,
@@ -46,6 +47,9 @@
"markup.changed": 50,
"constant.numeric.line-number.find-in-files - match": 51,
"entity.name.filename.find-in-files": 52,
+ "keyword.control": 53,
+ "entity.name.type": 54,
+ "string.interpolated": 55,
}
@@ -158,13 +162,13 @@ def parse_dict(self, node):
return d
def parse_array(self, node):
- l = []
+ new_arr = []
for child in node:
value = self.parse(child)
if value is not None:
- l.append(value)
+ new_arr.append(value)
- return l
+ return new_arr
def parse_text(self, node):
# If the node text is a hex color convert it to an xterm equivalent
diff --git a/suplemon/themes/monokai.tmTheme b/suplemon/themes/monokai.tmTheme
index 17459b7..fca3ecb 100644
--- a/suplemon/themes/monokai.tmTheme
+++ b/suplemon/themes/monokai.tmTheme
@@ -13,7 +13,7 @@
foreground
254
invisibles
- 237
+ 236
lineHighlight
237
selection
@@ -26,6 +26,28 @@
235
+
+ name
+ Generic
+ scope
+ generic
+ settings
+
+ foreground
+ 254
+
+
+
+ name
+ Punctuation
+ scope
+ punctuation
+ settings
+
+ foreground
+ 254
+
+
name
Comment
@@ -48,18 +70,28 @@
228
+
+ name
+ String Interpolated
+ scope
+ string.interpolated
+ settings
+
+ foreground
+ 135
+
+
name
Number
scope
- constant.numeric
+ constant.numeric, constant.language
settings
foreground
135
-
name
Built-in constant
@@ -103,7 +135,18 @@
settings
foreground
- 161
+ 197
+
+
+
+ name
+ Keyword Control
+ scope
+ keyword.control
+ settings
+
+ foreground
+ 197
@@ -132,6 +175,17 @@
75
+
+ name
+ Class name
+ scope
+ entity.name
+ settings
+
+ foreground
+ 155
+
+
name
Class name
@@ -142,7 +196,7 @@
fontStyle
italic
foreground
- 118
+ 155
@@ -158,6 +212,19 @@
81
+
+ name
+ Tag name type
+ scope
+ entity.name.type
+ settings
+
+ fontStyle
+ italic
+ foreground
+ 81
+
+
name
Inherited class
@@ -168,7 +235,20 @@
fontStyle
italic underline
foreground
- 118
+ 155
+
+
+
+ name
+ Inherited class
+ scope
+ entity.other.attribute-name
+ settings
+
+ fontStyle
+ italic underline
+ foreground
+ 155
@@ -181,7 +261,7 @@
fontStyle
italic
foreground
- 118
+ 155
@@ -197,6 +277,19 @@
214
+
+ name
+ Error
+ scope
+ invalid
+ settings
+
+ fontStyle
+ italic
+ foreground
+ 197
+
+
uuid
D8D5E82E-3D5B-46B5-B38E-8C841C21347D
diff --git a/suplemon/ui.py b/suplemon/ui.py
index 5b3305b..6fbfbcf 100644
--- a/suplemon/ui.py
+++ b/suplemon/ui.py
@@ -93,11 +93,12 @@ def _curses_key_name(self, key):
def __str__(self):
parts = [
- str(self.type),
- str(self.key_name),
- str(self.key_code),
- str(self.mouse_code),
- str(self.mouse_pos)
+ "type=" + str(self.type),
+ "key_name=" + str(self.key_name),
+ "key_code=" + str(self.key_code),
+ "curses_key_name=" + str(self.curses_key_name),
+ "mouse_code=" + str(self.mouse_code),
+ "mouse_pos=" + str(self.mouse_pos),
]
return " ".join(parts)
@@ -241,10 +242,10 @@ def setup_colors(self):
curses.init_pair(8, 8, curses.COLOR_BLACK) # 8 Gray on Black (Line number color)
curses.init_pair(9, 8, bg) # 8 Gray (Whitespace color)
except:
- self.logger.warning("Enhanced colors failed to load. You could try 'export TERM=xterm-256color'.")
+ self.logger.info("Enhanced colors failed to load. You could try 'export TERM=xterm-256color'.")
self.app.config["editor"]["theme"] = "8colors"
else:
- self.logger.warning("Enhanced colors not supported. You could try 'export TERM=xterm-256color'.")
+ self.logger.info("Enhanced colors not supported. You could try 'export TERM=xterm-256color'.")
self.app.config["editor"]["theme"] = "8colors"
self.app.themes.use(self.app.config["editor"]["theme"])
@@ -387,11 +388,11 @@ def file_list_str(self):
no_write_symbol = ["!", "\u2715"][self.app.config["app"]["use_unicode_symbols"]]
is_changed_symbol = ["*", "\u2732"][self.app.config["app"]["use_unicode_symbols"]]
for f in file_list:
- prepend = [no_write_symbol, ""][f.is_writable()]
+ prepend = no_write_symbol if not f.is_writable() else ""
append = ""
if self.app.config["display"]["show_file_modified_indicator"]:
append += ["", is_changed_symbol][f.is_changed()]
- fname = prepend + f.name + append
+ fname = prepend + (f.name if f.name else "untitled") + append
if not str_list:
str_list.append("[{0}]".format(fname))
else:
@@ -449,12 +450,13 @@ def show_legend(self):
"""Show keyboard legend."""
# Only the most important commands are displayed in the legend
legend_commands = [
- ("save_file", "Save"),
+ ("save", "Save"),
("save_file_as", "Save as"),
("reload_file", "Reload"),
("undo", "Undo"),
("redo", "Redo"),
- ("open", "Open"),
+ ("prompt_open_file", "Open"),
+ ("close", "Close"),
("copy", "Copy"),
("cut", "Cut"),
("insert", "Paste"),
@@ -467,7 +469,7 @@ def show_legend(self):
("run_command", "Run command"),
("toggle_mouse", "Mouse mode"),
("help", "Help"),
- ("ask_exit", "Exit"),
+ ("exit", "Exit"),
]
# Get the key bindings for the commands
diff --git a/suplemon/viewer.py b/suplemon/viewer.py
index 40ffcd6..15dde1f 100644
--- a/suplemon/viewer.py
+++ b/suplemon/viewer.py
@@ -66,21 +66,22 @@ def __init__(self, app, window):
# Runnable methods
self.operations = {
- "arrow_right": self.arrow_right, # Arrow Right
- "arrow_left": self.arrow_left, # Arrow Left
- "arrow_up": self.arrow_up, # Arrow Up
- "arrow_down": self.arrow_down, # Arrow Down
- "jump_left": self.jump_left, # Ctrl + Left
- "jump_right": self.jump_right, # Ctrl + Right
- "jump_up": self.jump_up, # Ctrl + Up
- "jump_down": self.jump_down, # Ctrl + Down
- "page_up": self.page_up, # Page Up
- "page_down": self.page_down, # Page Down
- "home": self.home, # Home
- "end": self.end, # End
- "find": self.find_query, # Ctrl + F
- "find_next": self.find_next, # Ctrl + D
- "find_all": self.find_all, # Ctrl + A
+ "arrow_right": self.arrow_right,
+ "arrow_left": self.arrow_left,
+ "arrow_up": self.arrow_up,
+ "arrow_down": self.arrow_down,
+ "jump_left": self.jump_left,
+ "jump_right": self.jump_right,
+ "jump_up": self.jump_up,
+ "jump_down": self.jump_down,
+ "page_up": self.page_up,
+ "page_down": self.page_down,
+ "home": self.home,
+ "end": self.end,
+ "find": self.find_query,
+ "find_next": self.find_next,
+ "find_all": self.find_all,
+ "scroll_lines": self.scroll_lines,
}
self.pygments_syntax = None # Needs to be implemented in derived classes
@@ -111,7 +112,7 @@ def scroll_pos(self):
@scroll_pos.setter
def scroll_pos(self, pos):
self.y_scroll = pos[0]
- self.x_scroll = pos[1]
+ self.x_scroll = pos[1] if pos[1] >= 0 else 0
def get_cursor(self):
"""Return the main cursor."""
@@ -408,6 +409,8 @@ def render_line_pygments(self, line, pos, x_offset, max_len):
else:
# Color with pygments
settings = self.app.themes.get_scope(scope)
+ if not settings:
+ self.logger.info("Theme settings for scope '{0}' of word '{1}' not found.".format(scope, token[1]))
pair = scope_to_pair.get(scope)
if settings and pair is not None:
fg = int(settings.get("foreground") or -1)
@@ -521,6 +524,11 @@ def render_cursors(self):
# Scrolling
###########################################################################
+ def scroll_lines(self, amount=1):
+ self.y_scroll += -amount
+ if self.y_scroll < 0:
+ self.y_scroll = 0
+
def scroll_up(self):
"""Scroll view up if neccesary."""
cursor = self.get_first_cursor()
@@ -661,23 +669,31 @@ def handle_input(self, event):
# Try match a key to a method and call it
key_bindings = self.get_key_bindings()
- operation = None
+
+ binding = None
if key in key_bindings:
- operation = key_bindings[key]
+ binding = key_bindings[key]
elif name in key_bindings:
- operation = key_bindings[name]
- if operation:
- self.run_operation(operation)
+ binding = key_bindings[name]
+
+ if binding:
+ args = binding["args"] if "args" in binding else {}
+ self.run_operation(binding["command"], args)
return True
+
return False
- def run_operation(self, operation):
+ def run_operation(self, operation, args={}):
"""Run an editor core operation."""
if operation in self.operations:
cancel = self.app.trigger_event_before(operation)
if cancel:
return False
- result = self.operations[operation]()
+ try:
+ result = self.operations[operation](**args)
+ except TypeError:
+ self.logger.exception("Invalid arguments for key binding!")
+ return False
self.app.trigger_event_after(operation)
return result
return False
@@ -804,14 +820,14 @@ def jump_right(self):
break
self.move_cursors()
- def jump_up(self):
- """Jump up 3 lines."""
- self.move_cursors((0, -3))
+ def jump_up(self, lines=3):
+ """Jump up n lines, defaults to 3."""
+ self.move_cursors((0, lines * -1))
self.scroll_up()
- def jump_down(self):
- """Jump down 3 lines."""
- self.move_cursors((0, 3))
+ def jump_down(self, lines=3):
+ """Jump down n lines, defaults to 3."""
+ self.move_cursors((0, lines))
self.scroll_down()
def find_query(self):
@@ -894,14 +910,16 @@ def find_next(self):
what = self.last_find
if what == "":
cursor = self.get_cursor()
- search = "^([\w\-]+)"
+ search = r"\b\w+\b"
line = self.lines[cursor.y][cursor.x:]
matches = re.match(search, line)
+
if matches:
what = matches.group(0)
else:
if line:
what = line[0]
+
# Escape the data if regex is enabled
if self.config["regex_find"]:
what = re.escape(what)
@@ -974,6 +992,7 @@ def setup_highlight(self):
if ext in self.extension_map:
ext = self.extension_map[ext] # Use it
try:
+ # TODO: improve lexer detection via pygments
self.pygments_syntax = pygments.lexers.get_lexer_by_name(ext)
self.logger.debug("Loaded Pygments lexer '{0}'.".format(ext))
except: