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: