From 1607f0e58dee75e888006d2161a2d6939fff6fec Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sun, 6 Mar 2016 21:20:44 +0000 Subject: [PATCH 01/32] Adds a color map to linelight to avoid hard coded color codes --- suplemon/linelight/color_map.py | 9 +++++++++ suplemon/linelight/css.py | 13 +++++++------ suplemon/linelight/html.py | 11 ++++++----- suplemon/linelight/js.py | 15 ++++++++------- suplemon/linelight/json.py | 7 ++++--- suplemon/linelight/md.py | 11 ++++++----- suplemon/linelight/php.py | 17 +++++++++-------- suplemon/linelight/py.py | 17 +++++++++-------- suplemon/main.py | 2 +- suplemon/viewer.py | 13 +++++++------ 10 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 suplemon/linelight/color_map.py diff --git a/suplemon/linelight/color_map.py b/suplemon/linelight/color_map.py new file mode 100644 index 0000000..8bccc30 --- /dev/null +++ b/suplemon/linelight/color_map.py @@ -0,0 +1,9 @@ +color_map = { + "red": 1, + "green": 2, + "yellow": 3, + "blue": 4, + "magenta": 5, + "cyan": 6, + "white": 7 +} diff --git a/suplemon/linelight/css.py b/suplemon/linelight/css.py index 5bd152f..386abde 100644 --- a/suplemon/linelight/css.py +++ b/suplemon/linelight/css.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,16 +7,16 @@ def get_comment(self): return ("/*", "*/") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() if helpers.starts(line, "@import"): - color = 4 # Blue + color = color_map["blue"] elif helpers.starts(line, "$"): - color = 2 # Green + color = color_map["green"] elif helpers.starts(line, "/*") or helpers.ends(line, "*/"): - color = 5 # Magenta + color = color_map["magenta"] elif helpers.starts(line, "{") or helpers.ends(line, "}") or helpers.ends(line, "{"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.ends(line, ";"): - color = 3 # Yellow + color = color_map["yellow"] return color diff --git a/suplemon/linelight/html.py b/suplemon/linelight/html.py index 976c0ed..8c05167 100644 --- a/suplemon/linelight/html.py +++ b/suplemon/linelight/html.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,14 +7,14 @@ def get_comment(self): return ("") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() if helpers.starts(line, ["#", "//", "/*", "*/", ""]): - color = 5 # Magenta + color = color_map["magenta"] elif helpers.starts(line, "<"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.ends(line, ">"): - color = 6 # Cyan + color = color_map["cyan"] return color diff --git a/suplemon/linelight/js.py b/suplemon/linelight/js.py index 29c812d..63c7a94 100644 --- a/suplemon/linelight/js.py +++ b/suplemon/linelight/js.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,18 +7,18 @@ def get_comment(self): return ("//", "") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() if helpers.starts(line, ["import", "from"]): - color = 4 # Blue + color = color_map["blue"] elif helpers.starts(line, "function"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["return"]): - color = 1 # Red + color = color_map["red"] elif helpers.starts(line, "this."): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["//", "/*", "*/", "*"]): - color = 5 # Magenta + color = color_map["magenta"] elif helpers.starts(line, ["if", "else", "for ", "while ", "continue", "break"]): - color = 3 # Yellow + color = color_map["yellow"] return color diff --git a/suplemon/linelight/json.py b/suplemon/linelight/json.py index e138e8b..8a7c682 100644 --- a/suplemon/linelight/json.py +++ b/suplemon/linelight/json.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,10 +7,10 @@ def get_comment(self, line): return ("", "") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() if helpers.starts(line, ["{", "}"]): - color = 3 # Blue + color = color_map["yellow"] elif helpers.starts(line, "\""): - color = 2 # Green + color = color_map["green"] return color diff --git a/suplemon/linelight/md.py b/suplemon/linelight/md.py index 7cf8674..d10830a 100644 --- a/suplemon/linelight/md.py +++ b/suplemon/linelight/md.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,14 +7,14 @@ def get_comment(self, line): return ("", "") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() if helpers.starts(line, ["*", "-"]): # List - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, "#"): # Header - color = 2 # Green + color = color_map["green"] elif helpers.starts(line, ">"): # Item desription - color = 3 # Yellow + color = color_map["yellow"] elif helpers.starts(raw_line, " "): # Code - color = 5 # Magenta + color = color_map["magenta"] return color diff --git a/suplemon/linelight/php.py b/suplemon/linelight/php.py index 8e736e0..8f8f043 100644 --- a/suplemon/linelight/php.py +++ b/suplemon/linelight/php.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,22 +7,22 @@ def get_comment(self): return ("//", "") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() keywords = ["if", "else", "finally", "try", "catch", "foreach", "while", "continue", "pass", "break"] if helpers.starts(line, ["include", "require"]): - color = 4 # Blue + color = color_map["blue"] elif helpers.starts(line, ["class", "public", "private", "function"]): - color = 2 # Green + color = color_map["green"] elif helpers.starts(line, "def"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["return"]): - color = 1 # Red + color = color_map["red"] elif helpers.starts(line, "$"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["#", "//", "/*", "*/"]): - color = 5 # Magenta + color = color_map["magenta"] elif helpers.starts(line, keywords): - color = 3 # Yellow + color = color_map["yellow"] return color diff --git a/suplemon/linelight/py.py b/suplemon/linelight/py.py index 32a5bb9..c149ebe 100644 --- a/suplemon/linelight/py.py +++ b/suplemon/linelight/py.py @@ -1,4 +1,5 @@ from suplemon import helpers +from suplemon.linelight.color_map import color_map class Syntax: @@ -6,22 +7,22 @@ def get_comment(self): return ("# ", "") def get_color(self, raw_line): - color = 7 + color = color_map["white"] line = raw_line.strip() keywords = ["if", "elif", "else", "finally", "try", "except", "for ", "while ", "continue", "pass", "break"] if helpers.starts(line, ["import", "from"]): - color = 4 # Blue + color = color_map["blue"] elif helpers.starts(line, "class"): - color = 2 # Green + color = color_map["green"] elif helpers.starts(line, "def"): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["return", "yield"]): - color = 1 # Red + color = color_map["red"] elif helpers.starts(line, "self."): - color = 6 # Cyan + color = color_map["cyan"] elif helpers.starts(line, ["#", "//", "\"", "'", ":"]): - color = 5 # Magenta + color = color_map["magenta"] elif helpers.starts(line, keywords): - color = 3 # Yellow + color = color_map["yellow"] return color diff --git a/suplemon/main.py b/suplemon/main.py index e44aa5a..1fa4f3f 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -149,7 +149,7 @@ def load(self): self.load_files() self.running = 1 self.trigger_event_after("app_loaded") - + def on_input(self, event): # Handle the input or give it to the editor if not self.handle_input(event): diff --git a/suplemon/viewer.py b/suplemon/viewer.py index 6f9e3dd..3419a12 100644 --- a/suplemon/viewer.py +++ b/suplemon/viewer.py @@ -5,14 +5,14 @@ import os import sys -import imp import curses import logging - +import importlib from .line import Line from .cursor import Cursor from .themes import scope_to_pair +import suplemon.linelight # NOQA try: import pygments.lexers @@ -73,9 +73,10 @@ def setup_linelight(self): filename = ext + ".py" path = os.path.join(curr_path, "linelight", filename) module = False + syntax_module_name = ".{}".format(ext) if os.path.isfile(path): try: - module = imp.load_source(ext, path) + module = importlib.import_module(syntax_module_name, "suplemon.linelight") except: self.logger.error("Failed to load syntax file '{0}'!".format(path), exc_info=True) else: @@ -309,7 +310,7 @@ def render(self): """Render the editor curses window.""" if self.app.block_rendering: return - + self.window.erase() i = 0 max_y = self.get_size()[1] @@ -462,7 +463,7 @@ def render_cursors(self): """Render editor window cursors.""" if self.app.block_rendering: return - + max_x, max_y = self.get_size() for cursor in self.cursors: x = cursor.x - self.x_scroll + self.line_offset() @@ -530,7 +531,7 @@ def move_cursors(self, delta=None, noupdate=False): """Move all cursors with delta. To avoid refreshing the screen set noupdate to True.""" if self.app.block_rendering: noupdate = True - + for cursor in self.cursors: if delta: if delta[0] != 0 and cursor.x >= 0: From 79cef87dbef5191491dd3b272f8ccac6993cb9d9 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 26 Feb 2017 15:00:49 +0200 Subject: [PATCH 02/32] Added shift+tab for going backwards when autocompleting files. --- suplemon/prompt.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/suplemon/prompt.py b/suplemon/prompt.py index e274990..97fcb47 100644 --- a/suplemon/prompt.py +++ b/suplemon/prompt.py @@ -160,12 +160,17 @@ def handle_input(self, event): self.autocomplete() # Don't pass the event to the parent class return False + elif name == "shift+tab": + # Go to previous item when shift+tab is pressed + self.autocomplete(previous=True) + # Don't pass the event to the parent class + return False else: # If any key other than tab is pressed deactivate the auto completer self.deactivate_autocomplete() Prompt.handle_input(self, event) - def autocomplete(self): + def autocomplete(self, previous=False): data = self.get_data() # Use the current input by default if self.complete_active: # If the completer is active use the its initial input value data = self.complete_data @@ -188,9 +193,16 @@ def autocomplete(self): self.complete_index = 0 else: # Increment the selected item countor - self.complete_index += 1 - if self.complete_index > len(items)-1: - self.complete_index = 0 + if previous: + # Go back + self.complete_index -= 1 + if self.complete_index < 0: + self.complete_index = len(items)-1 + else: + # Go forward + self.complete_index += 1 + if self.complete_index > len(items)-1: + self.complete_index = 0 item = items[self.complete_index] new_data = os.path.join(os.path.dirname(data), item) From fa01b1bd70e2d53406c0014ca62ffb5e9ff54394 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 26 Feb 2017 15:01:28 +0200 Subject: [PATCH 03/32] Lower log level for non existing files. --- suplemon/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/file.py b/suplemon/file.py index 51d5d5b..986c5a1 100644 --- a/suplemon/file.py +++ b/suplemon/file.py @@ -113,7 +113,7 @@ def load(self, read=True): return True path = self._path() if not os.path.isfile(path): - self.logger.info("Given path isn't a file.") + self.logger.debug("Given path isn't a file.") return False data = self._read(path) if data is False: From 1e522317cc149c41a0e768766249004c7600abe0 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 26 Feb 2017 15:02:37 +0200 Subject: [PATCH 04/32] Added and fixed some keybindings. --- suplemon/key_mappings.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/suplemon/key_mappings.py b/suplemon/key_mappings.py index 418b2e8..d7bb225 100644 --- a/suplemon/key_mappings.py +++ b/suplemon/key_mappings.py @@ -124,6 +124,14 @@ "O5R": "ctrl+f3", "O5S": "ctrl+f4", + "KEY_F(29)": "ctrl+f5", + "KEY_F(30)": "ctrl+f6", + "KEY_F(31)": "ctrl+f7", + "KEY_F(32)": "ctrl+f8", + "KEY_F(33)": "ctrl+f9", + "KEY_F(34)": "ctrl+f10", + "KEY_F(35)": "ctrl+f11", + "KEY_F(36)": "ctrl+f12", # Alt 563: "alt+up", @@ -145,6 +153,14 @@ "kPRV3": "alt+pageup", "kNXT3": "alt+pagedown", + "KEY_F(53)": "alt+f5", + "KEY_F(54)": "alt+f6", + "KEY_F(55)": "alt+f7", + "KEY_F(56)": "alt+f8", + "KEY_F(57)": "alt+f9", + "KEY_F(58)": "alt+f10", + "KEY_F(59)": "alt+f11", + "KEY_F(60)": "alt+f12", # Shift curses.KEY_SLEFT: "shift+left", @@ -166,6 +182,15 @@ "O2R": "shift+f3", "O2S": "shift+f4", + "KEY_F(17)": "shift+f5", + "KEY_F(18)": "shift+f6", + "KEY_F(19)": "shift+f7", + "KEY_F(20)": "shift+f8", + "KEY_F(21)": "shift+f9", + "KEY_F(22)": "shift+f10", + "KEY_F(23)": "shift+f11", + "KEY_F(24)": "shift+f12", + # Alt Gr "O1P": "altgr+f1", @@ -192,9 +217,9 @@ "kLFT6": "ctrl+shift+left", "kRIT6": "ctrl+shift+right", - "kDC6": "ctrl+alt+delete", - "kHOM6": "ctrl+alt+home", - "kEND6": "ctrl+alt+end", + "kDC6": "ctrl+shift+delete", + "kHOM6": "ctrl+shift+home", + "kEND6": "ctrl+shift+end", # Control + Alt From 38b871399f3d0dcb2de89b3016e0504986d1b11e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 26 Feb 2017 15:03:02 +0200 Subject: [PATCH 05/32] Catch all errors. --- suplemon/modules/hostname.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/modules/hostname.py b/suplemon/modules/hostname.py index 0c5df00..bd51e03 100644 --- a/suplemon/modules/hostname.py +++ b/suplemon/modules/hostname.py @@ -13,7 +13,7 @@ def init(self): hostinfo = None try: hostinfo = socket.gethostbyaddr(socket.gethostname()) - except socket.gaierror: + except: self.logger.debug("Failed to get hostname.") if hostinfo: self.hostname = hostinfo[0] From 2414a69741d3435f9ea7971ca00b1833691c496a Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 23 Mar 2017 00:20:28 -0700 Subject: [PATCH 06/32] Add support for the MacOS native pasteboard via pbcopy/pbpaste. (#183) * Add support for the MacOS native pasteboard via pbcopy/pbpaste. --- suplemon/main.py | 2 +- suplemon/modules/system_clipboard.py | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/suplemon/main.py b/suplemon/main.py index 6a74491..24a1903 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -18,7 +18,7 @@ from .config import Config from .editor import Editor -__version__ = "0.1.58" +__version__ = "0.1.59" class App: diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 0ec1b3a..165a630 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -8,8 +8,12 @@ class SystemClipboard(Module): def init(self): self.init_logging(__name__) - if not self.has_xsel_support(): - self.logger.warning("Can't use system clipboard. Install 'xsel' for system clipboard support.") + if self.has_xsel_support(): + self.clipboard_type = "xsel" + elif self.has_pb_support(): + self.clipboard_type = "pb" + else: + self.logger.warning("Can't use system clipboard. Install 'xsel' or 'pbcopy' for system clipboard support.") return False self.bind_event_before("insert", self.insert) self.bind_event_after("copy", self.copy) @@ -27,19 +31,35 @@ def insert(self, event): def get_clipboard(self): try: - data = subprocess.check_output(["xsel", "-b"], universal_newlines=True) + if self.clipboard_type == "xsel": + command = ["xsel", "-b"] + elif self.clipboard_type == "pb": + command = ["pbpaste", "-Prefer", "txt"] + else: + return False + data = subprocess.check_output(command, universal_newlines=True) return data except: return False def set_clipboard(self, data): try: - p = subprocess.Popen(["xsel", "-i", "-b"], stdin=subprocess.PIPE) + if self.clipboard_type == "xsel": + command = ["xsel", "-i", "-b"] + elif self.clipboard_type == "pb": + command = ["pbcopy"] + else: + return False + p = subprocess.Popen(command, stdin=subprocess.PIPE) out, err = p.communicate(input=bytes(data, "utf-8")) return out except: return False + def has_pb_support(self): + output = self.get_output(["which", "pbcopy"]) + return output + def has_xsel_support(self): output = self.get_output(["xsel", "--version"]) return output From 3dc6f9c9adddda351fdc78ec9ae4c6352709185a Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Thu, 23 Mar 2017 09:50:34 +0200 Subject: [PATCH 07/32] Updated changelog. --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e4b59b..04ebfc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Change Log ========== +## [v0.1.60](https://github.com/richrd/suplemon/tree/v0.1.60) (2017-03-23) compared to previous master branch. +[Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.59...v0.1.60) + +**Implemented enhancements:** + +- Add support for the MacOS native pasteboard via pbcopy/pbpaste. Credit @abl +- Added shift+tab for going backwards when autocompleting files. +- Added F keys with modifiers and fixed some ctrl+shift keybindings. + +**Fixed bugs:** + +- Broader error handling in hostname module. +- Don't print log message when opening file that doesn't exist. + + ## [v0.1.59](https://github.com/richrd/suplemon/tree/v0.1.59) (2017-02-16) compared to previous master branch. [Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.58...v0.1.59) From 1b6bfa85dc3df9c72d71f2a8a536f4132cef768f Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Mon, 29 May 2017 17:34:19 +0300 Subject: [PATCH 08/32] Don't show unicode symbols by default. Causes problems on too many terminals. --- suplemon/config/defaults.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/config/defaults.json b/suplemon/config/defaults.json index 5bbd4a7..aed50f8 100644 --- a/suplemon/config/defaults.json +++ b/suplemon/config/defaults.json @@ -19,7 +19,7 @@ // How long curses will wait to detect ESC key "escdelay": 50, // Wether to use special unicode symbols for decoration - "use_unicode_symbols": true + "use_unicode_symbols": false }, // Editor settings "editor": { From d7dbfb9a9afb1d0b8fe3388557e005372ec0c03e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Mon, 29 May 2017 17:42:10 +0300 Subject: [PATCH 09/32] Updated changelog. --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ebfc0..9a086e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change Log ========== +## [v0.1.61](https://github.com/richrd/suplemon/tree/v0.1.61) (2017-05-29) compared to previous master branch. +[Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.60...v0.1.61) + +**Fixed bugs:** + +- Disable fancy unicode symbols by default. Caused problems on some terminals. + + ## [v0.1.60](https://github.com/richrd/suplemon/tree/v0.1.60) (2017-03-23) compared to previous master branch. [Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.59...v0.1.60) From 693a158631617177cae6cb581d2a4e159a1716b3 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 22 Aug 2017 11:44:39 +0300 Subject: [PATCH 10/32] Fix #187 by measureing string width with wide character aware wcswidth. --- setup.py | 3 ++- suplemon/ui.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 691d6b9..ef9cba1 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,8 @@ package_data={"": files}, include_package_data=True, install_requires=[ - "pygments" + "pygments", + "wcwidth" ], entry_points={ "console_scripts": ["suplemon=suplemon.cli:main"] diff --git a/suplemon/ui.py b/suplemon/ui.py index b91cbd2..81bcd57 100644 --- a/suplemon/ui.py +++ b/suplemon/ui.py @@ -6,6 +6,7 @@ import os import sys import logging +from wcwidth import wcswidth from .prompt import Prompt, PromptBool, PromptFile from .key_mappings import key_map @@ -326,9 +327,7 @@ def show_top_status(self): head_parts.append(self.file_list_str()) head = " ".join(head_parts) - head = head + (" " * (self.screen.getmaxyx()[1]-len(head)-1)) - if len(head) >= size[0]: - head = head[:size[0]-1] + head = head + (" " * (self.screen.getmaxyx()[1]-wcswidth(head)-1)) if self.app.config["display"]["invert_status_bars"]: self.header_win.addstr(0, 0, head, curses.color_pair(0) | curses.A_REVERSE) else: From 4a26cc6038438675a4e8e805b87a7fa9abfb880a Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 22 Aug 2017 11:55:55 +0300 Subject: [PATCH 11/32] Re-enable unicode symbols by default. --- suplemon/config/defaults.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/config/defaults.json b/suplemon/config/defaults.json index aed50f8..5bbd4a7 100644 --- a/suplemon/config/defaults.json +++ b/suplemon/config/defaults.json @@ -19,7 +19,7 @@ // How long curses will wait to detect ESC key "escdelay": 50, // Wether to use special unicode symbols for decoration - "use_unicode_symbols": false + "use_unicode_symbols": true }, // Editor settings "editor": { From 267c8207c65f98dec2f03a8aae0a17928eac951d Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 22 Aug 2017 12:02:57 +0300 Subject: [PATCH 12/32] Updated changelog. --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a086e9..29b56fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change Log ========== +## [v0.1.62](https://github.com/richrd/suplemon/tree/v0.1.62) (2017-08-22) compared to previous master branch. +[Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.61...v0.1.62) + +**Fixed bugs:** + +- Fixed and re-enabled fancy unicode symbols. + + ## [v0.1.61](https://github.com/richrd/suplemon/tree/v0.1.61) (2017-05-29) compared to previous master branch. [Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.60...v0.1.61) From dd75f511220466fdeeb4e36f8072156e3c203f97 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 22 Aug 2017 12:24:18 +0300 Subject: [PATCH 13/32] Added extra safety to top status bar rendering. Related to #187. --- suplemon/ui.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/suplemon/ui.py b/suplemon/ui.py index 81bcd57..bc0dff7 100644 --- a/suplemon/ui.py +++ b/suplemon/ui.py @@ -327,11 +327,17 @@ def show_top_status(self): head_parts.append(self.file_list_str()) head = " ".join(head_parts) - head = head + (" " * (self.screen.getmaxyx()[1]-wcswidth(head)-1)) - if self.app.config["display"]["invert_status_bars"]: - self.header_win.addstr(0, 0, head, curses.color_pair(0) | curses.A_REVERSE) - else: - self.header_win.addstr(0, 0, head, curses.color_pair(0)) + head = head + (" " * (size[0]-wcswidth(head)-1)) + head_width = wcswidth(head) + if head_width > size[0]: + head = head[:size[0]-head_width] + try: + if self.app.config["display"]["invert_status_bars"]: + self.header_win.addstr(0, 0, head, curses.color_pair(0) | curses.A_REVERSE) + else: + self.header_win.addstr(0, 0, head, curses.color_pair(0)) + except: + pass self.header_win.refresh() def file_list_str(self): From 556deb6db15d513708a7cc546caaae9b44608811 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 22 Aug 2017 12:27:08 +0300 Subject: [PATCH 14/32] Update config. --- suplemon/config/defaults.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/config/defaults.json b/suplemon/config/defaults.json index aed50f8..5bbd4a7 100644 --- a/suplemon/config/defaults.json +++ b/suplemon/config/defaults.json @@ -19,7 +19,7 @@ // How long curses will wait to detect ESC key "escdelay": 50, // Wether to use special unicode symbols for decoration - "use_unicode_symbols": false + "use_unicode_symbols": true }, // Editor settings "editor": { From 44f31f47116040cf0bfd241c55412e945f8b1ac4 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sat, 14 Oct 2017 16:48:08 +0100 Subject: [PATCH 15/32] Uses enumerate in for loops Replaces while i< max loops with i=0, i+=1 by for i in range(max) loops --- suplemon/editor.py | 9 +++------ suplemon/main.py | 4 +--- suplemon/modules/autodocstring.py | 4 +--- suplemon/modules/linter.py | 4 +--- suplemon/modules/tabstospaces.py | 4 +--- suplemon/viewer.py | 4 +--- 6 files changed, 8 insertions(+), 21 deletions(-) diff --git a/suplemon/editor.py b/suplemon/editor.py index edbeb97..4a1d813 100644 --- a/suplemon/editor.py +++ b/suplemon/editor.py @@ -484,13 +484,12 @@ def copy(self): copy_buffer = [] # Get all lines with cursors on them line_nums = self.get_lines_with_cursors() - i = 0 - while i < len(line_nums): + + for i in range(len(line_nums)): # Get the line line = self.lines[line_nums[i]] # Put it in our temporary buffer copy_buffer.append(line.get_data()) - i += 1 self.set_buffer(copy_buffer) self.store_action_state("copy") @@ -502,8 +501,7 @@ def cut(self): line_nums = self.get_lines_with_cursors() # Sort from last to first (invert order) line_nums = line_nums[::-1] - i = 0 - while i < len(line_nums): # Iterate from last to first + for i in range(len(line_nums)): # Iterate from last to first # Make sure we don't completely remove the last line if len(self.lines) == 1: cut_buffer.append(self.lines[0]) @@ -517,7 +515,6 @@ def cut(self): cut_buffer.append(line) # Move all cursors below the current line up self.move_y_cursors(line_no, -1) - i += 1 self.move_cursors() # Make sure cursors are in valid places # Reverse the buffer to get correct order and store it self.set_buffer(cut_buffer[::-1]) diff --git a/suplemon/main.py b/suplemon/main.py index 15ba8ee..f6774a9 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -175,14 +175,12 @@ def main_loop(self): got_input = False # Run through max 100 inputs (so the view is updated at least every 100 characters) - i = 0 - while i < self.max_input: + for i in range(self.max_input): event = self.ui.get_input(False) # non-blocking if not event: break # no more inputs to process at this time - i += 1 got_input = True self.on_input(event) diff --git a/suplemon/modules/autodocstring.py b/suplemon/modules/autodocstring.py index dfff5ca..947e5d7 100644 --- a/suplemon/modules/autodocstring.py +++ b/suplemon/modules/autodocstring.py @@ -126,15 +126,13 @@ def get_function_returns(self, editor, line_number): :param line_number: Line number of the function definition. :return: Boolean indicating wether the function something. """ - i = line_number+1 - while i < len(editor.lines): + for i in range(line_number+1, len(editor.lines)): line = editor.get_line(i) data = line.get_data().strip() if helpers.starts(data, "def "): break if helpers.starts(data, "return "): return True - i += 1 return False diff --git a/suplemon/modules/linter.py b/suplemon/modules/linter.py index 65ff15e..787bc1c 100644 --- a/suplemon/modules/linter.py +++ b/suplemon/modules/linter.py @@ -80,8 +80,7 @@ def lint_file(self, file): return False editor = file.get_editor() - line_no = 0 - while line_no < len(editor.lines): + for line_no in range(len(editor.lines)): line = editor.lines[line_no] if line_no+1 in linting.keys(): line.linting = linting[line_no+1] @@ -89,7 +88,6 @@ def lint_file(self, file): else: line.linting = False line.reset_number_color() - line_no += 1 def get_msgs_on_line(self, editor, line_no): line = editor.lines[line_no] diff --git a/suplemon/modules/tabstospaces.py b/suplemon/modules/tabstospaces.py index 68d5229..90e60a1 100644 --- a/suplemon/modules/tabstospaces.py +++ b/suplemon/modules/tabstospaces.py @@ -5,11 +5,9 @@ class TabsToSpaces(Module): def run(self, app, editor, args): - i = 0 - for line in editor.lines: + for i, line in enumerate(editor.lines): new = line.data.replace("\t", " "*editor.config["tab_width"]) editor.lines[i].set_data(new) - i += 1 module = { diff --git a/suplemon/viewer.py b/suplemon/viewer.py index 706608a..05c1334 100644 --- a/suplemon/viewer.py +++ b/suplemon/viewer.py @@ -312,11 +312,10 @@ def render(self): return self.window.erase() - i = 0 max_y = self.get_size()[1] max_len = self.max_line_length() # Iterate through visible lines - while i < max_y: + for i in range(max_y): x_offset = self.line_offset() lnum = i + self.y_scroll if lnum >= len(self.lines): # Make sure we have a line to show @@ -333,7 +332,6 @@ def render(self): except: self.logger.error("Failed rendering line #{0} @{1} DATA:'{2}'!".format(lnum+1, pos, line), exc_info=True) - i += 1 self.render_cursors() def render_line_contents(self, line, pos, x_offset, max_len): From 6d1de29db489cb310542c8b206fb672824f4c608 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sun, 15 Oct 2017 19:36:20 +0100 Subject: [PATCH 16/32] Replaces 0 and 1 by Boolean literals --- suplemon/main.py | 24 +++++++++++------------- suplemon/prompt.py | 10 +++++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/suplemon/main.py b/suplemon/main.py index f6774a9..fac1205 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -30,9 +30,9 @@ def __init__(self, filenames=None, config_file=None): :param str filenames[*]: Path to a file to load """ self.version = __version__ - self.inited = 0 - self.running = 0 - self.debug = 1 + self.inited = False + self.running = False + self.debug = True self.block_rendering = False # Set default variables @@ -110,14 +110,14 @@ def init(self): self.themes = themes.ThemeLoader(self) # Indicate that initialization is complete - self.inited = 1 + self.inited = True return True def exit(self): """Stop the main loop and exit.""" self.trigger_event_before("app_exit") - self.running = 0 + self.running = False def run(self): """Run the app via the ui wrapper.""" @@ -157,7 +157,7 @@ def load(self): "Python 3.3 or higher." .format(version=ver)) self.load_files() - self.running = 1 + self.running = True self.trigger_event_after("app_loaded") def on_input(self, event): @@ -528,14 +528,12 @@ def trigger_event_after(self, event): def toggle_fullscreen(self): """Toggle full screen editor.""" display = self.config["display"] + show_indicators = True if display["show_top_bar"]: - display["show_top_bar"] = 0 - display["show_bottom_bar"] = 0 - display["show_legend"] = 0 - else: - display["show_top_bar"] = 1 - display["show_bottom_bar"] = 1 - display["show_legend"] = 1 + show_indicators = False + display["show_top_bar"] = show_indicators + display["show_bottom_bar"] = show_indicators + display["show_legend"] = show_indicators # Virtual curses windows need to be resized self.ui.resize() diff --git a/suplemon/prompt.py b/suplemon/prompt.py index d7a3b5e..e4d2dd5 100644 --- a/suplemon/prompt.py +++ b/suplemon/prompt.py @@ -12,8 +12,8 @@ class Prompt(Editor): """An input prompt based on the Editor.""" def __init__(self, app, window): Editor.__init__(self, app, window) - self.ready = 0 - self.canceled = 0 + self.ready = False + self.canceled = False self.input_func = lambda: False self.caption = "" @@ -34,14 +34,14 @@ def set_input_source(self, input_func): def on_ready(self): """Accepts the current input.""" - self.ready = 1 + self.ready = True return def on_cancel(self): """Cancels the input prompt.""" self.set_data("") - self.ready = 1 - self.canceled = 1 + self.ready = True + self.canceled = True return def line_offset(self): From 13fb7656a8026f1fa880d3a9d023175cea442996 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sun, 22 Oct 2017 18:12:45 +0100 Subject: [PATCH 17/32] declares attributes in constructors of App, UI and BaseViewer --- suplemon/main.py | 5 +++++ suplemon/ui.py | 7 +++++++ suplemon/viewer.py | 15 +++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/suplemon/main.py b/suplemon/main.py index fac1205..b030645 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -44,6 +44,11 @@ def __init__(self, filenames=None, config_file=None): self.global_buffer = [] self.event_bindings = {} + self.config = None + self.ui = None + self.modules = None + self.themes = None + # Maximum amount of inputs to process at once self.max_input = 100 diff --git a/suplemon/ui.py b/suplemon/ui.py index b6e0737..0062e8c 100644 --- a/suplemon/ui.py +++ b/suplemon/ui.py @@ -108,6 +108,13 @@ def __init__(self, app): self.logger = logging.getLogger(__name__) self.warned_old_curses = 0 self.limited_colors = True + self.screen = None + self.current_yx = None + self.text_input = None + self.header_win = None + self.status_win = None + self.editor_win = None + self.legend_win = None def init(self): """Set ESC delay and then import curses.""" diff --git a/suplemon/viewer.py b/suplemon/viewer.py index 05c1334..1e4ce19 100644 --- a/suplemon/viewer.py +++ b/suplemon/viewer.py @@ -83,6 +83,9 @@ def __init__(self, app, window): "find_all": self.find_all, # Ctrl + A } + self.pygments_syntax = None # Needs to be implemented in derived classes + self.lexer = None # Needs to be implemented in derived classes + def init(self): pass @@ -234,6 +237,12 @@ def set_single_cursor(self, cursor): """Discard all cursors and place a new one.""" self.cursors = [Cursor(cursor)] + def setup_linelight(self): + raise NotImplementedError("Needs to be implemented in derived classes") + + def setup_highlight(self): + raise NotImplementedError("Needs to be implemented in derived classes") + def set_file_extension(self, ext): """Set the file extension.""" ext = ext.lower() @@ -407,6 +416,9 @@ def render_line_pygments(self, line, pos, x_offset, max_len): first_token = False x_offset += len(text) + def get_line_color(self, line): + raise NotImplementedError("Needs to be implemented in derived classes") + def render_line_linelight(self, line, pos, x_offset, max_len): """Render line with naive line based highlighting.""" y = pos[1] @@ -803,6 +815,9 @@ def find_query(self): if what: self.find(what) + def store_action_state(self, state): + raise NotImplementedError("Needs to be implemented in derived classes") + def find(self, what, findall=False): """Find what in data (from top to bottom). Adds a cursor when found.""" # Sorry for this colossal function From 2c661336a1e512eb8f0d86ef1b65492b91855807 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sat, 4 Nov 2017 19:50:23 +0000 Subject: [PATCH 18/32] Removes unused accessors and swaps scroll_pos for properties in BaseViewer --- suplemon/modules/application_state.py | 6 +++--- suplemon/viewer.py | 22 +++++++--------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/suplemon/modules/application_state.py b/suplemon/modules/application_state.py index db81054..423bab4 100644 --- a/suplemon/modules/application_state.py +++ b/suplemon/modules/application_state.py @@ -31,8 +31,8 @@ def get_file_state(self, file): """Get the state of a single file.""" editor = file.get_editor() state = { - "cursors": [cursor.tuple() for cursor in editor.get_cursors()], - "scroll_pos": editor.get_scroll_pos(), + "cursors": [cursor.tuple() for cursor in editor.cursors], + "scroll_pos": editor.scroll_pos, "hash": self.get_hash(editor), } return state @@ -47,7 +47,7 @@ def get_hash(self, editor): def set_file_state(self, file, state): """Set the state of a file.""" file.editor.set_cursors(state["cursors"]) - file.editor.set_scroll_pos(state["scroll_pos"]) + file.editor.scroll_pos = state["scroll_pos"] def store_states(self): """Store the states of opened files.""" diff --git a/suplemon/viewer.py b/suplemon/viewer.py index 1e4ce19..2fad74c 100644 --- a/suplemon/viewer.py +++ b/suplemon/viewer.py @@ -104,23 +104,19 @@ def get_size(self): y, x = self.window.getmaxyx() return (x, y) - def get_scroll_pos(self): - return (self.y_scroll, self.x_scroll) + @property + def scroll_pos(self): + return self.y_scroll, self.x_scroll - def get_y_scroll(self): - return self.y_scroll - - def get_x_scroll(self): - return self.x_scroll + @scroll_pos.setter + def scroll_pos(self, pos): + self.y_scroll = pos[0] + self.x_scroll = pos[1] def get_cursor(self): """Return the main cursor.""" return self.cursors[0] - def get_cursors(self): - """Return list of all cursors.""" - return self.cursors - def get_first_cursor(self): """Get the first (primary) cursor.""" highest = None @@ -208,10 +204,6 @@ def set_config(self, config): self.config = config self.set_cursor_style(self.config["cursor_style"]) - def set_scroll_pos(self, pos): - self.y_scroll = pos[0] - self.x_scroll = pos[1] - def set_cursor_style(self, cursor_style): """Set cursor style. From 21dd495aaf2b393931e35ec8cb492a0183b4ab56 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sat, 4 Nov 2017 20:28:27 +0000 Subject: [PATCH 19/32] Replaces calls to helpers.starts and helpers.ends by native str.startswith and str.endswith --- suplemon/config.py | 3 +-- suplemon/helpers.py | 21 --------------------- suplemon/linelight/css.py | 11 +++++------ suplemon/linelight/html.py | 9 ++++----- suplemon/linelight/js.py | 11 +++++------ suplemon/linelight/json.py | 5 ++--- suplemon/linelight/md.py | 9 ++++----- suplemon/linelight/php.py | 19 +++++++++---------- suplemon/linelight/py.py | 19 +++++++++---------- suplemon/modules/autocomplete.py | 2 +- suplemon/modules/autodocstring.py | 6 +++--- suplemon/modules/comment.py | 4 ++-- 12 files changed, 45 insertions(+), 74 deletions(-) diff --git a/suplemon/config.py b/suplemon/config.py index 4eabde3..c39be5b 100644 --- a/suplemon/config.py +++ b/suplemon/config.py @@ -7,7 +7,6 @@ import json import logging -from . import helpers from . import suplemon_module @@ -161,7 +160,7 @@ def remove_config_comments(self, data): cleaned = [] for line in lines: line = line.strip() - if helpers.starts(line, "//") or helpers.starts(line, "#"): + if line.startswith(("//", "#")): continue cleaned.append(line) return "\n".join(cleaned) diff --git a/suplemon/helpers.py b/suplemon/helpers.py index fdd26aa..409fdd3 100644 --- a/suplemon/helpers.py +++ b/suplemon/helpers.py @@ -19,27 +19,6 @@ def curr_time_sec(): return time.strftime("%H:%M:%S") -def starts(s, what): - """Check if a string begins with given string or any one in given list.""" - if isinstance(what, str): - what = [what] - for item in what: - if s.find(item) == 0: - return True - return False - - -def ends(s, what): - """Check if a string ends with given string or any one in given list.""" - s = s[::-1] - if isinstance(what, str): - what = [what] - for item in what: - if s.find(item[::-1]) == 0: - return True - return False - - def multisplit(data, delimiters): pattern = "|".join(map(re.escape, delimiters)) return re.split(pattern, data) diff --git a/suplemon/linelight/css.py b/suplemon/linelight/css.py index 386abde..e44f35d 100644 --- a/suplemon/linelight/css.py +++ b/suplemon/linelight/css.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,14 +8,14 @@ def get_comment(self): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - if helpers.starts(line, "@import"): + if line.startswith("@import"): color = color_map["blue"] - elif helpers.starts(line, "$"): + elif line.startswith("$"): color = color_map["green"] - elif helpers.starts(line, "/*") or helpers.ends(line, "*/"): + elif line.startswith("/*") or line.endswith("*/"): color = color_map["magenta"] - elif helpers.starts(line, "{") or helpers.ends(line, "}") or helpers.ends(line, "{"): + elif line.startswith("{") or line.endswith(("}", "{")): color = color_map["cyan"] - elif helpers.ends(line, ";"): + elif line.endswith(";"): color = color_map["yellow"] return color diff --git a/suplemon/linelight/html.py b/suplemon/linelight/html.py index 8c05167..92b49c7 100644 --- a/suplemon/linelight/html.py +++ b/suplemon/linelight/html.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,12 +8,12 @@ def get_comment(self): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - if helpers.starts(line, ["#", "//", "/*", "*/", ""]): + elif line.endswith(("*/", "-->")): color = color_map["magenta"] - elif helpers.starts(line, "<"): + elif line.startswith("<"): color = color_map["cyan"] - elif helpers.ends(line, ">"): + elif line.endswith(">"): color = color_map["cyan"] return color diff --git a/suplemon/linelight/js.py b/suplemon/linelight/js.py index fe93ec5..c98e653 100644 --- a/suplemon/linelight/js.py +++ b/suplemon/linelight/js.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,14 +8,14 @@ def get_comment(self): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - if helpers.starts(line, "function"): + if line.startswith("function"): color = color_map["cyan"] - elif helpers.starts(line, ["return"]): + elif line.startswith("return"): color = color_map["red"] - elif helpers.starts(line, "this."): + elif line.startswith("this."): color = color_map["cyan"] - elif helpers.starts(line, ["//", "/*", "*/", "*"]): + elif line.startswith(("//", "/*", "*/", "*")): color = color_map["magenta"] - elif helpers.starts(line, ["if", "else", "for ", "while ", "continue", "break"]): + elif line.startswith(("if", "else", "for ", "while ", "continue", "break")): color = color_map["yellow"] return color diff --git a/suplemon/linelight/json.py b/suplemon/linelight/json.py index 8a7c682..6cc0bf8 100644 --- a/suplemon/linelight/json.py +++ b/suplemon/linelight/json.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,8 +8,8 @@ def get_comment(self, line): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - if helpers.starts(line, ["{", "}"]): + if line.startswith(("{", "}")): color = color_map["yellow"] - elif helpers.starts(line, "\""): + elif line.startswith("\""): color = color_map["green"] return color diff --git a/suplemon/linelight/md.py b/suplemon/linelight/md.py index d10830a..d033f08 100644 --- a/suplemon/linelight/md.py +++ b/suplemon/linelight/md.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,12 +8,12 @@ def get_comment(self, line): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - if helpers.starts(line, ["*", "-"]): # List + if line.startswith(("*", "-")): # List color = color_map["cyan"] - elif helpers.starts(line, "#"): # Header + elif line.startswith("#"): # Header color = color_map["green"] - elif helpers.starts(line, ">"): # Item desription + elif line.startswith(">"): # Item description color = color_map["yellow"] - elif helpers.starts(raw_line, " "): # Code + elif raw_line.startswith(" "): # Code color = color_map["magenta"] return color diff --git a/suplemon/linelight/php.py b/suplemon/linelight/php.py index 8f8f043..0854e2f 100644 --- a/suplemon/linelight/php.py +++ b/suplemon/linelight/php.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,20 +8,20 @@ def get_comment(self): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - keywords = ["if", "else", "finally", "try", "catch", "foreach", - "while", "continue", "pass", "break"] - if helpers.starts(line, ["include", "require"]): + keywords = ("if", "else", "finally", "try", "catch", "foreach", + "while", "continue", "pass", "break") + if line.startswith(("include", "require")): color = color_map["blue"] - elif helpers.starts(line, ["class", "public", "private", "function"]): + elif line.startswith(("class", "public", "private", "function")): color = color_map["green"] - elif helpers.starts(line, "def"): + elif line.startswith("def"): color = color_map["cyan"] - elif helpers.starts(line, ["return"]): + elif line.startswith("return"): color = color_map["red"] - elif helpers.starts(line, "$"): + elif line.startswith("$"): color = color_map["cyan"] - elif helpers.starts(line, ["#", "//", "/*", "*/"]): + elif line.startswith(("#", "//", "/*", "*/")): color = color_map["magenta"] - elif helpers.starts(line, keywords): + elif line.startswith(keywords): color = color_map["yellow"] return color diff --git a/suplemon/linelight/py.py b/suplemon/linelight/py.py index c149ebe..6abfc35 100644 --- a/suplemon/linelight/py.py +++ b/suplemon/linelight/py.py @@ -1,4 +1,3 @@ -from suplemon import helpers from suplemon.linelight.color_map import color_map @@ -9,20 +8,20 @@ def get_comment(self): def get_color(self, raw_line): color = color_map["white"] line = raw_line.strip() - keywords = ["if", "elif", "else", "finally", "try", "except", - "for ", "while ", "continue", "pass", "break"] - if helpers.starts(line, ["import", "from"]): + keywords = ("if", "elif", "else", "finally", "try", "except", + "for ", "while ", "continue", "pass", "break") + if line.startswith(("import", "from")): color = color_map["blue"] - elif helpers.starts(line, "class"): + elif line.startswith("class"): color = color_map["green"] - elif helpers.starts(line, "def"): + elif line.startswith("def"): color = color_map["cyan"] - elif helpers.starts(line, ["return", "yield"]): + elif line.startswith(("return", "yield")): color = color_map["red"] - elif helpers.starts(line, "self."): + elif line.startswith("self."): color = color_map["cyan"] - elif helpers.starts(line, ["#", "//", "\"", "'", ":"]): + elif line.startswith(("#", "//", "\"", "'", ":")): color = color_map["magenta"] - elif helpers.starts(line, keywords): + elif line.startswith(keywords): color = color_map["yellow"] return color diff --git a/suplemon/modules/autocomplete.py b/suplemon/modules/autocomplete.py index bfe131b..953bf7b 100644 --- a/suplemon/modules/autocomplete.py +++ b/suplemon/modules/autocomplete.py @@ -61,7 +61,7 @@ def get_match(self, word): # Build list of suitable matches candidates = [] for candidate in self.word_list: - if helpers.starts(candidate, word) and len(candidate) > len(word): + if candidate.startswith(word) and len(candidate) > len(word): candidates.append(candidate) # Find the shortest match # TODO: implement cycling through matches diff --git a/suplemon/modules/autodocstring.py b/suplemon/modules/autodocstring.py index 947e5d7..ecc91be 100644 --- a/suplemon/modules/autodocstring.py +++ b/suplemon/modules/autodocstring.py @@ -34,7 +34,7 @@ def run(self, app, editor, args): cursor = editor.get_cursor() line = editor.get_line(cursor.y) line_data = line.get_data() - if not helpers.starts(line_data.strip(), "def "): + if not line_data.strip().startswith("def "): app.set_status("Current line isn't a function definition.") return False @@ -129,9 +129,9 @@ def get_function_returns(self, editor, line_number): for i in range(line_number+1, len(editor.lines)): line = editor.get_line(i) data = line.get_data().strip() - if helpers.starts(data, "def "): + if data.startswith("def "): break - if helpers.starts(data, "return "): + if data.startswith("return "): return True return False diff --git a/suplemon/modules/comment.py b/suplemon/modules/comment.py index 50eb04c..644852b 100644 --- a/suplemon/modules/comment.py +++ b/suplemon/modules/comment.py @@ -24,13 +24,13 @@ def run(self, app, editor, args): target = str(line).strip() w = helpers.whitespace(line) # Amount of whitespace at line start # If the line starts with comment syntax - if helpers.starts(target, comment[0]): + if target.startswith(comment[0]): # Reconstruct the whitespace and add the line new_line = (" "*w) + line[w+len(comment[0]):] # If comment end syntax exists if comment[1]: # Try to remove it from the end of the line - if helpers.ends(new_line, comment[1]): + if new_line.endswith(comment[1]): new_line = new_line[:-1*len(comment[1])] # Store the modified line # editor.lines[lnum] = Line(new_line) From dd4005f21dd70b0b0481cec0df0d46eda4919fc1 Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sat, 4 Nov 2017 22:10:46 +0000 Subject: [PATCH 20/32] Replaces pad_lnum by native zfill --- suplemon/viewer.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/suplemon/viewer.py b/suplemon/viewer.py index 2fad74c..565f82d 100644 --- a/suplemon/viewer.py +++ b/suplemon/viewer.py @@ -248,14 +248,6 @@ def add_cursor(self, cursor): """Add a new cursor. Accepts a x,y tuple or a Cursor instance.""" self.cursors.append(Cursor(cursor)) - def pad_lnum(self, n): - """Pad line number with zeroes.""" - # TODO: move to helpers - s = str(n) - while len(s) < self.line_offset()-1: - s = "0" + s - return s - def max_line_length(self): """Get maximum line length that fits in the editor.""" return self.get_size()[0]-self.line_offset()-1 @@ -325,7 +317,8 @@ def render(self): line = self.lines[lnum] if self.config["show_line_nums"]: curs_color = curses.color_pair(line.number_color) - self.window.addstr(i, 0, self.pad_lnum(lnum+1)+" ", curs_color) + padded_num = str(lnum+1).zfill(self.line_offset()-1) + self.window.addstr(i, 0, padded_num+" ", curs_color) pos = (x_offset, i) try: From dd5ddc2658ccc6b26e035dd5c558691a89120eaf Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 5 Nov 2017 17:11:59 +0200 Subject: [PATCH 21/32] Module docs WIP --- suplemon/module_loader.py | 8 ++++---- suplemon/modules/application_state.py | 6 ++++++ suplemon/modules/autocomplete.py | 2 +- suplemon/modules/clock.py | 1 + suplemon/modules/comment.py | 2 +- suplemon/modules/config.py | 1 + suplemon/modules/date.py | 2 ++ suplemon/modules/diff.py | 1 + suplemon/modules/eval.py | 6 ++++++ suplemon/modules/keymap.py | 1 + suplemon/modules/linter.py | 2 ++ suplemon/modules/lower.py | 2 ++ suplemon/modules/lstrip.py | 2 ++ suplemon/modules/paste.py | 2 ++ suplemon/modules/reload.py | 1 + suplemon/modules/replace_all.py | 2 ++ suplemon/modules/reverse.py | 2 ++ suplemon/modules/rstrip.py | 3 ++- suplemon/modules/save.py | 2 ++ suplemon/modules/save_all.py | 2 ++ suplemon/modules/strip.py | 3 ++- suplemon/modules/system_clipboard.py | 2 ++ suplemon/modules/tabstospaces.py | 2 ++ suplemon/modules/toggle_whitespace.py | 2 ++ suplemon/modules/upper.py | 2 ++ 25 files changed, 53 insertions(+), 8 deletions(-) diff --git a/suplemon/module_loader.py b/suplemon/module_loader.py index dfdc7c3..e837b9f 100644 --- a/suplemon/module_loader.py +++ b/suplemon/module_loader.py @@ -23,8 +23,8 @@ def load(self): self.logger.debug("Loading modules...") dirlist = os.listdir(self.module_path) for item in dirlist: - # Skip 'hidden' dot files - if item[0] == ".": + # Skip 'hidden' dot files and files beginning with and underscore + if item.startswith((".", "_")): continue parts = item.split(".") if len(parts) < 2: @@ -32,8 +32,8 @@ def load(self): name = parts[0] ext = parts[-1] - # only load .py modules that don't begin with an underscore - if ext == "py" and name[0] != "_": + # only load .py modules + if ext == "py": module = self.load_single(name) if module: # Load and store the module instance diff --git a/suplemon/modules/application_state.py b/suplemon/modules/application_state.py index db81054..2ff1ed7 100644 --- a/suplemon/modules/application_state.py +++ b/suplemon/modules/application_state.py @@ -7,6 +7,12 @@ class ApplicationState(Module): + """ + Stores the state of open files when exiting the editor and restores when files are reopened. + + Cursor positions and scroll position are stored and restored. + """ + def init(self): self.init_logging(__name__) self.bind_event_after("app_loaded", self.on_load) diff --git a/suplemon/modules/autocomplete.py b/suplemon/modules/autocomplete.py index bfe131b..8a363a0 100644 --- a/suplemon/modules/autocomplete.py +++ b/suplemon/modules/autocomplete.py @@ -10,7 +10,7 @@ class AutoComplete(Module): """ A simple autocompletion module. - This module adds autocomplete support for the tab event. It uses a word + This adds autocomplete support for the tab key. It uses a word list scanned from all open files for completions. By default it suggests the shortest possible match. If there are no matches, the tab action is run normally. diff --git a/suplemon/modules/clock.py b/suplemon/modules/clock.py index 7fbed3d..473a069 100644 --- a/suplemon/modules/clock.py +++ b/suplemon/modules/clock.py @@ -7,6 +7,7 @@ class Clock(Module): """Shows a clock in the top status bar.""" + def get_status(self): s = time.strftime("%H:%M") if self.app.config["app"]["use_unicode_symbols"]: diff --git a/suplemon/modules/comment.py b/suplemon/modules/comment.py index 50eb04c..de51fc0 100644 --- a/suplemon/modules/comment.py +++ b/suplemon/modules/comment.py @@ -5,7 +5,7 @@ class Comment(Module): - """Toggles line commenting based on current file syntax.""" + """Toggle line commenting based on current file syntax.""" def run(self, app, editor, args): """Comment the current line(s).""" diff --git a/suplemon/modules/config.py b/suplemon/modules/config.py index 0bda121..66bf32d 100644 --- a/suplemon/modules/config.py +++ b/suplemon/modules/config.py @@ -7,6 +7,7 @@ class SuplemonConfig(config.ConfigModule): """Shortcut to openning the keymap config file.""" + def init(self): self.config_name = "defaults.json" self.config_default_path = os.path.join(self.app.path, "config", self.config_name) diff --git a/suplemon/modules/date.py b/suplemon/modules/date.py index b98fcde..ecc2e4e 100644 --- a/suplemon/modules/date.py +++ b/suplemon/modules/date.py @@ -6,6 +6,8 @@ class Date(Module): + """Shows the current date without year in the top status bar.""" + def get_status(self): s = time.strftime("%d.%m.") if self.app.config["app"]["use_unicode_symbols"]: diff --git a/suplemon/modules/diff.py b/suplemon/modules/diff.py index 5add943..4e79c42 100644 --- a/suplemon/modules/diff.py +++ b/suplemon/modules/diff.py @@ -7,6 +7,7 @@ class Diff(Module): """View a diff of the current file compared to it's on disk version.""" + def run(self, app, editor, args): curr_file = app.get_file() curr_path = curr_file.get_path() diff --git a/suplemon/modules/eval.py b/suplemon/modules/eval.py index 0135bfd..c83f8ee 100644 --- a/suplemon/modules/eval.py +++ b/suplemon/modules/eval.py @@ -4,6 +4,12 @@ class Eval(Module): + """ + Evaluate a python expression. + + If no expression is provided the current line(s) are treated as the expression. + """ + def run(self, app, editor, args): if not args: return self.evaluate_lines(editor) diff --git a/suplemon/modules/keymap.py b/suplemon/modules/keymap.py index df48ca6..a126d82 100644 --- a/suplemon/modules/keymap.py +++ b/suplemon/modules/keymap.py @@ -7,6 +7,7 @@ class KeymapConfig(config.ConfigModule): """Shortcut to openning the keymap config file.""" + def init(self): self.config_name = "keymap.json" self.config_default_path = os.path.join(self.app.path, "config", self.config_name) diff --git a/suplemon/modules/linter.py b/suplemon/modules/linter.py index 65ff15e..1717a4a 100644 --- a/suplemon/modules/linter.py +++ b/suplemon/modules/linter.py @@ -9,6 +9,8 @@ class Linter(Module): + """Linter for suplemon.""" + def init(self): self.init_logging(__name__) diff --git a/suplemon/modules/lower.py b/suplemon/modules/lower.py index a4e1824..0679dc3 100644 --- a/suplemon/modules/lower.py +++ b/suplemon/modules/lower.py @@ -4,6 +4,8 @@ class Lower(Module): + """Transform current lines to lower case.""" + def run(self, app, editor, args): line_nums = [] for cursor in editor.cursors: diff --git a/suplemon/modules/lstrip.py b/suplemon/modules/lstrip.py index 32b8497..f4cab49 100644 --- a/suplemon/modules/lstrip.py +++ b/suplemon/modules/lstrip.py @@ -4,6 +4,8 @@ class LStrip(Module): + """Trim whitespace from beginning of current lines.""" + def run(self, app, editor, args): # TODO: move cursors in sync with line contents line_nums = editor.get_lines_with_cursors() diff --git a/suplemon/modules/paste.py b/suplemon/modules/paste.py index 130e255..ecbf547 100644 --- a/suplemon/modules/paste.py +++ b/suplemon/modules/paste.py @@ -4,6 +4,8 @@ class Paste(Module): + """Toggle paste mode (helpful when pasting over SSH if auto indent is enabled)""" + def init(self): # Flag for paste mode self.active = False diff --git a/suplemon/modules/reload.py b/suplemon/modules/reload.py index f00971d..2434e27 100644 --- a/suplemon/modules/reload.py +++ b/suplemon/modules/reload.py @@ -5,6 +5,7 @@ class Reload(Module): """Reload all addon modules.""" + def run(self, app, editor, args): self.app.modules.load() diff --git a/suplemon/modules/replace_all.py b/suplemon/modules/replace_all.py index c5d2e3c..356cdcd 100644 --- a/suplemon/modules/replace_all.py +++ b/suplemon/modules/replace_all.py @@ -4,6 +4,8 @@ class ReplaceAll(Module): + """Replace all occurences in all files of given text with given replacement.""" + def run(self, app, editor, args): r_from = self.app.ui.query("Replace text:") if not r_from: diff --git a/suplemon/modules/reverse.py b/suplemon/modules/reverse.py index c131334..1db659c 100644 --- a/suplemon/modules/reverse.py +++ b/suplemon/modules/reverse.py @@ -4,6 +4,8 @@ class Reverse(Module): + """Reverse text on current lines.""" + def run(self, app, editor, args): line_nums = [] for cursor in editor.cursors: diff --git a/suplemon/modules/rstrip.py b/suplemon/modules/rstrip.py index c41e98f..8eb6e00 100644 --- a/suplemon/modules/rstrip.py +++ b/suplemon/modules/rstrip.py @@ -4,7 +4,8 @@ class RStrip(Module): - """Strips whitespace from end of line.""" + """Trim whitespace from the end of lines.""" + def run(self, app, editor, args): line_nums = editor.get_lines_with_cursors() for n in line_nums: diff --git a/suplemon/modules/save.py b/suplemon/modules/save.py index 3bd76b5..24db054 100644 --- a/suplemon/modules/save.py +++ b/suplemon/modules/save.py @@ -4,6 +4,8 @@ class Save(Module): + """Save the current file.""" + def run(self, app, editor, args): return app.save_file() diff --git a/suplemon/modules/save_all.py b/suplemon/modules/save_all.py index 54b4fe8..d608c83 100644 --- a/suplemon/modules/save_all.py +++ b/suplemon/modules/save_all.py @@ -4,6 +4,8 @@ class SaveAll(Module): + """Save all currently open files. Asks for confirmation.""" + def run(self, app, editor, args): if not self.app.ui.query_bool("Save all files?", False): return False diff --git a/suplemon/modules/strip.py b/suplemon/modules/strip.py index 75373a4..af16325 100644 --- a/suplemon/modules/strip.py +++ b/suplemon/modules/strip.py @@ -4,7 +4,8 @@ class Strip(Module): - """Strips whitespace from start and end of line.""" + """Trim whitespace from start and end of lines.""" + def run(self, app, editor, args): line_nums = editor.get_lines_with_cursors() for n in line_nums: diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 165a630..65fbb01 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -6,6 +6,8 @@ class SystemClipboard(Module): + """Integrates the system clipboard with suplemon.""" + def init(self): self.init_logging(__name__) if self.has_xsel_support(): diff --git a/suplemon/modules/tabstospaces.py b/suplemon/modules/tabstospaces.py index 68d5229..7a87f37 100644 --- a/suplemon/modules/tabstospaces.py +++ b/suplemon/modules/tabstospaces.py @@ -4,6 +4,8 @@ class TabsToSpaces(Module): + """Convert tab characters to spaces in the entire file.""" + def run(self, app, editor, args): i = 0 for line in editor.lines: diff --git a/suplemon/modules/toggle_whitespace.py b/suplemon/modules/toggle_whitespace.py index fc985f4..30b0173 100644 --- a/suplemon/modules/toggle_whitespace.py +++ b/suplemon/modules/toggle_whitespace.py @@ -4,6 +4,8 @@ class ToggleWhitespace(Module): + """Toggle visually showing whitespace.""" + def run(self, app, editor, args): # Toggle the boolean new_value = not self.app.config["editor"]["show_white_space"] diff --git a/suplemon/modules/upper.py b/suplemon/modules/upper.py index b79f4ab..9706699 100644 --- a/suplemon/modules/upper.py +++ b/suplemon/modules/upper.py @@ -4,6 +4,8 @@ class Upper(Module): + """Transform current lines to upper case.""" + def run(self, app, editor, args): line_nums = [] for cursor in editor.cursors: From 2575d646170559f7bdb5864f8d96823fa2a8157f Mon Sep 17 00:00:00 2001 From: Gnewbee Date: Sun, 19 Nov 2017 19:59:19 +0000 Subject: [PATCH 22/32] Simplifies syntax for toggle_full_screen (ternary to boolean) --- suplemon/main.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/suplemon/main.py b/suplemon/main.py index b030645..3880a6b 100644 --- a/suplemon/main.py +++ b/suplemon/main.py @@ -533,9 +533,7 @@ def trigger_event_after(self, event): def toggle_fullscreen(self): """Toggle full screen editor.""" display = self.config["display"] - show_indicators = True - if display["show_top_bar"]: - show_indicators = False + show_indicators = not display["show_top_bar"] display["show_top_bar"] = show_indicators display["show_bottom_bar"] = show_indicators display["show_legend"] = show_indicators From 5a10873d2641e9ec4fccc4f0cfca22de4e293392 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Mon, 27 Nov 2017 22:30:00 +0200 Subject: [PATCH 23/32] Add sort lines module. --- suplemon/modules/sort_lines.py | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 suplemon/modules/sort_lines.py diff --git a/suplemon/modules/sort_lines.py b/suplemon/modules/sort_lines.py new file mode 100644 index 0000000..6994f2d --- /dev/null +++ b/suplemon/modules/sort_lines.py @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 + +from suplemon.suplemon_module import Module + + +class SortLines(Module): + """ + Sort current lines. + + Sorts alphabetically by default. + Add 'length' to sort by length. + Add 'reverse' to reverse the sorting. + """ + + def sort_normal(self, line): + return line.data + + def sort_length(self, line): + return len(line.data) + + def run(self, app, editor, args): + args = args.lower() + + sorter = self.sort_normal + reverse = True if "reverse" in args else False + if "length" in args: + sorter = self.sort_length + + indices = editor.get_lines_with_cursors() + lines = [editor.get_line(i) for i in indices] + + sorted_lines = sorted(lines, key=sorter, reverse=reverse) + + for i, line in enumerate(sorted_lines): + editor.lines[indices[i]] = line + + +module = { + "class": SortLines, + "name": "sort_lines", +} From c88c4d7f572876bbb13f7eddff725e3b1a5e5bd0 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 28 Nov 2017 19:44:28 +0200 Subject: [PATCH 24/32] Add bulk delete module. --- suplemon/modules/bulk_delete.py | 75 +++++++++++++++++++++++++++++++++ suplemon/prompt.py | 24 +++++++++++ suplemon/ui.py | 8 +++- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 suplemon/modules/bulk_delete.py diff --git a/suplemon/modules/bulk_delete.py b/suplemon/modules/bulk_delete.py new file mode 100644 index 0000000..87cb9e3 --- /dev/null +++ b/suplemon/modules/bulk_delete.py @@ -0,0 +1,75 @@ +# -*- encoding: utf-8 + +from suplemon.suplemon_module import Module + + +class BulkDelete(Module): + """ + Bulk delete lines and characters. + Asks what direction to delete in by default. + + Add 'up' to delete lines above highest cursor. + Add 'down' to delete lines below lowest cursor. + Add 'left' to delete characters to the left of all cursors. + Add 'right' to delete characters to the right of all cursors. + """ + + def init(self): + self.directions = ["up", "down", "left", "right"] + + def handler(self, prompt, event): + # Get arrow keys from prompt + if event.key_name in self.directions: + prompt.set_data(event.key_name) + prompt.on_ready() + return True # Disable normal key handling + + def run(self, app, editor, args): + direction = args.lower() + if not direction: + direction = app.ui.query_filtered("Press arrow key in direction to delete:", handler=self.handler) + + if direction not in self.directions: + app.set_status("Invalid direction.") + return False + + # Delete entire lines + if direction == "up": + pos = editor.get_first_cursor() + length = len(editor.lines) + editor.lines = editor.lines[pos.y:] + delta = length - len(editor.lines) + # If lines were removed, move the cursors up the same amount + if delta: + editor.move_cursors((0, -delta)) + + elif direction == "down": + pos = editor.get_last_cursor() + editor.lines = editor.lines[:pos.y+1] + + # Delete from start or end of lines + else: + # Select min/max function based on direction + func = min if direction == "left" else max + # Get all lines with cursors + line_indices = editor.get_lines_with_cursors() + for line_no in line_indices: + # Get all cursors for the line + line_cursors = editor.get_cursors_on_line(line_no) + # Get the leftmost of rightmost x coordinate + x = func(line_cursors, key=lambda c: c.x).x + + # Delete correct part of the line + line = editor.lines[line_no] + if direction == "left": + line.data = line.data[x:] + # Also move cursors appropriately when deleting left side + [c.move_left(x) for c in line_cursors] + else: + line.data = line.data[:x] + + +module = { + "class": BulkDelete, + "name": "bulk_delete", +} diff --git a/suplemon/prompt.py b/suplemon/prompt.py index e4d2dd5..d9c9731 100644 --- a/suplemon/prompt.py +++ b/suplemon/prompt.py @@ -129,6 +129,30 @@ def get_input(self, caption="", initial=False): return False +class PromptFiltered(Prompt): + """An input prompt that allows intercepting and filtering input events.""" + + def __init__(self, app, window, handler=None): + Prompt.__init__(self, app, window) + self.prompt_handler = handler + + def handle_input(self, event): + """Handle special bindings for the prompt.""" + # The cancel and accept keys are kept for concistency + if event.key_name in ["ctrl+c", "escape"]: + self.on_cancel() + return False + if event.key_name == "enter": + self.on_ready() + return False + + if self.prompt_handler and self.prompt_handler(self, event): + # If the prompt handler returns True the default action is skipped + return True + + return Editor.handle_input(self, event) + + class PromptAutocmp(Prompt): """An input prompt with basic autocompletion.""" diff --git a/suplemon/ui.py b/suplemon/ui.py index 0062e8c..5793985 100644 --- a/suplemon/ui.py +++ b/suplemon/ui.py @@ -8,7 +8,7 @@ import logging from wcwidth import wcswidth -from .prompt import Prompt, PromptBool, PromptFile, PromptAutocmp +from .prompt import Prompt, PromptBool, PromptFiltered, PromptFile, PromptAutocmp from .key_mappings import key_map # Curses can't be imported yet but we'll @@ -509,6 +509,12 @@ def query_bool(self, text, default=False): result = self._query(text, default, PromptBool) return result + def query_filtered(self, text, initial="", handler=None): + """Get an arbitrary string from the user with input filtering.""" + prompt_inst = PromptFiltered(self.app, self.status_win, handler=handler) + result = self._query(text, initial, inst=prompt_inst) + return result + def query_file(self, text, initial=""): """Get a file path from the user.""" result = self._query(text, initial, PromptFile) From 08b0229c9ab3d6a58aa19018357632220ef5c99b Mon Sep 17 00:00:00 2001 From: Chris Tam Date: Mon, 11 Dec 2017 06:26:55 +0800 Subject: [PATCH 25/32] Add xclip support --- suplemon/modules/system_clipboard.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 165a630..9fcd9ba 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -12,8 +12,10 @@ def init(self): self.clipboard_type = "xsel" elif self.has_pb_support(): self.clipboard_type = "pb" + elif self.has_xclip_support(): + self.clipboard_type = "xclip" else: - self.logger.warning("Can't use system clipboard. Install 'xsel' or 'pbcopy' for system clipboard support.") + self.logger.warning("Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.") return False self.bind_event_before("insert", self.insert) self.bind_event_after("copy", self.copy) @@ -35,6 +37,8 @@ def get_clipboard(self): command = ["xsel", "-b"] elif self.clipboard_type == "pb": command = ["pbpaste", "-Prefer", "txt"] + elif self.clipboard_type == "xclip": + command = ["xclip", "-selection", "clipboard", "-out"] else: return False data = subprocess.check_output(command, universal_newlines=True) @@ -48,6 +52,8 @@ def set_clipboard(self, data): command = ["xsel", "-i", "-b"] elif self.clipboard_type == "pb": command = ["pbcopy"] + elif self.clipboard_type == "xclip": + command = ["xclip", "-selection", "clipboard", "-in"] else: return False p = subprocess.Popen(command, stdin=subprocess.PIPE) @@ -64,6 +70,10 @@ def has_xsel_support(self): output = self.get_output(["xsel", "--version"]) return output + def has_xclip_support(self): + output = self.get_output(["which", "xclip"]) # xclip -version outputs to stderr + return output + def get_output(self, cmd): try: process = subprocess.Popen(cmd, stdout=subprocess.PIPE) From 6cee0c4c39e71668050863d6d48d01530d0878cd Mon Sep 17 00:00:00 2001 From: Chris Tam Date: Mon, 11 Dec 2017 15:51:34 +0800 Subject: [PATCH 26/32] Fix a line too long --- suplemon/modules/system_clipboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/suplemon/modules/system_clipboard.py b/suplemon/modules/system_clipboard.py index 9fcd9ba..4700829 100644 --- a/suplemon/modules/system_clipboard.py +++ b/suplemon/modules/system_clipboard.py @@ -15,7 +15,8 @@ def init(self): elif self.has_xclip_support(): self.clipboard_type = "xclip" else: - self.logger.warning("Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.") + self.logger.warning( + "Can't use system clipboard. Install 'xsel' or 'pbcopy' or 'xclip' for system clipboard support.") return False self.bind_event_before("insert", self.insert) self.bind_event_after("copy", self.copy) From dd1d188a14077fa00cf3afd2859a6ed49c6485d1 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 12 Dec 2017 20:09:43 +0200 Subject: [PATCH 27/32] Added simple doc extractor to module loader. --- suplemon/module_loader.py | 44 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/suplemon/module_loader.py b/suplemon/module_loader.py index e837b9f..5ee6e16 100644 --- a/suplemon/module_loader.py +++ b/suplemon/module_loader.py @@ -21,6 +21,18 @@ def __init__(self, app=None): def load(self): """Find and load available modules.""" self.logger.debug("Loading modules...") + names = self.get_module_names() + for name in names: + module = self.load_single(name) + if module: + # Load and store the module instance + inst = self.load_instance(module) + if inst: + self.modules[module[0]] = inst + + def get_module_names(self): + """Get names of loadable modules.""" + names = [] dirlist = os.listdir(self.module_path) for item in dirlist: # Skip 'hidden' dot files and files beginning with and underscore @@ -28,18 +40,15 @@ def load(self): continue parts = item.split(".") if len(parts) < 2: + # Can't find file extension continue name = parts[0] ext = parts[-1] - # only load .py modules - if ext == "py": - module = self.load_single(name) - if module: - # Load and store the module instance - inst = self.load_instance(module) - if inst: - self.modules[module[0]] = inst + if ext != "py": + continue + names.append(name) + return names def load_instance(self, module): """Initialize a module.""" @@ -64,7 +73,24 @@ def load_single(self, name): mod.module["status"] = False return name, mod.module + def extract_docs(self): + """Get names and docs of runnable modules and print as markdown.""" + names = sorted(self.get_module_names()) + for name in names: + name, module = self.load_single(name) + # Skip modules that can't be run expicitly + if module["class"].run.__module__ == "suplemon.suplemon_module": + continue + # Skip undocumented modules + if not module["class"].__doc__: + continue + docstring = module["class"].__doc__ + docstring = "\n " + docstring.strip() + + doc = "- {0} \n{1}\n".format(name, docstring) + print(doc) + if __name__ == "__main__": ml = ModuleLoader() - ml.load() + ml.extract_docs() From b9b2423ff525476f8442084a525ffa6078b8aedf Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sat, 16 Dec 2017 01:18:44 +0200 Subject: [PATCH 28/32] Updated readme and module docstrings. --- README.md | 2 +- suplemon/modules/application_state.py | 2 +- suplemon/modules/config.py | 2 +- suplemon/modules/eval.py | 5 +++-- suplemon/modules/reload.py | 2 +- suplemon/modules/reverse.py | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d7d5fd1..bf29516 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ No dependencies outside the Python Standard Library required. * Flake8 > For showing linting for Python files. - * xsel + * xsel or xclip > For system clipboard support on X Window (Linux). * pbcopy / pbpaste diff --git a/suplemon/modules/application_state.py b/suplemon/modules/application_state.py index 2ff1ed7..6d4f6ec 100644 --- a/suplemon/modules/application_state.py +++ b/suplemon/modules/application_state.py @@ -9,7 +9,7 @@ class ApplicationState(Module): """ Stores the state of open files when exiting the editor and restores when files are reopened. - + Cursor positions and scroll position are stored and restored. """ diff --git a/suplemon/modules/config.py b/suplemon/modules/config.py index 66bf32d..0f6fee7 100644 --- a/suplemon/modules/config.py +++ b/suplemon/modules/config.py @@ -6,7 +6,7 @@ class SuplemonConfig(config.ConfigModule): - """Shortcut to openning the keymap config file.""" + """Shortcut for openning the config files.""" def init(self): self.config_name = "defaults.json" diff --git a/suplemon/modules/eval.py b/suplemon/modules/eval.py index c83f8ee..8e3bd92 100644 --- a/suplemon/modules/eval.py +++ b/suplemon/modules/eval.py @@ -5,9 +5,10 @@ class Eval(Module): """ - Evaluate a python expression. + Evaluate a python expression and show the result in the status bar. - If no expression is provided the current line(s) are treated as the expression. + If no expression is provided the current line(s) are evaluated and + replaced with the evaluation result. """ def run(self, app, editor, args): diff --git a/suplemon/modules/reload.py b/suplemon/modules/reload.py index 2434e27..ee443ee 100644 --- a/suplemon/modules/reload.py +++ b/suplemon/modules/reload.py @@ -4,7 +4,7 @@ class Reload(Module): - """Reload all addon modules.""" + """Reload all add-on modules.""" def run(self, app, editor, args): self.app.modules.load() diff --git a/suplemon/modules/reverse.py b/suplemon/modules/reverse.py index 1db659c..47937fe 100644 --- a/suplemon/modules/reverse.py +++ b/suplemon/modules/reverse.py @@ -4,7 +4,7 @@ class Reverse(Module): - """Reverse text on current lines.""" + """Reverse text on current line(s).""" def run(self, app, editor, args): line_nums = [] From f64cf0ff504c13f5b16dcee87f7bcad1a0bdc059 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 17 Dec 2017 15:12:57 +0200 Subject: [PATCH 29/32] Fix typo. --- suplemon/modules/replace_all.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suplemon/modules/replace_all.py b/suplemon/modules/replace_all.py index 356cdcd..b093a53 100644 --- a/suplemon/modules/replace_all.py +++ b/suplemon/modules/replace_all.py @@ -4,7 +4,7 @@ class ReplaceAll(Module): - """Replace all occurences in all files of given text with given replacement.""" + """Replace all occurrences in all files of given text with given replacement.""" def run(self, app, editor, args): r_from = self.app.ui.query("Replace text:") From 2b53766ef05c10563dab15b34b09fd8a785180a4 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 17 Dec 2017 15:33:03 +0200 Subject: [PATCH 30/32] Add command docs to readme and remove some old/outdated parts. --- README.md | 133 +++++++++++++++++++++++++++++--------- suplemon/module_loader.py | 2 +- 2 files changed, 104 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index bf29516..bb90cd5 100644 --- a/README.md +++ b/README.md @@ -97,13 +97,6 @@ It is as easy as nano, and has much of the power of Sublime Text. It also suppor to allow all kinds of customizations. To get more help hit ```Ctrl + H``` in the editor. Suplemon is licensed under the MIT license. -## Goals - 1. [X] Create a command line text editor with built in multi cursor support. It's awesome! - 2. [X] Usability should be even better and easier than nano. It's on par with desktop editors. - 3. [X] Multi cursor should be comparable to Sublime Text. - 4. [X] Develop Suplemon with Suplemon!!! I've used Suplemon for a long time as my main - editor (replacing ST and nano) for all developement, Git commits and everything else. - ## Configuration ### Main Config @@ -215,6 +208,109 @@ To view the default keymap file run ```keymap default``` * Scroll Wheel Up / Down > Scroll up & down. +## Commands + +Suplemon has various add-ons that implement extra features. +The commands can be run with Ctrl + E and the prompt has autocomplete to make running them faster. +The available commands and their descriptions are: + + * autocomplete + + A simple autocompletion module. + + This adds autocomplete support for the tab key. It uses a word + list scanned from all open files for completions. By default it suggests + the shortest possible match. If there are no matches, the tab action is + run normally. + + * autodocstring + + Simple module for adding docstring placeholders. + + This module is intended to generate docstrings for Python functions. + It adds placeholders for descriptions, arguments and return data. + Function arguments are crudely parsed from the function definition + and return statements are scanned from the function body. + + * comment + + Toggle line commenting based on current file syntax. + + * config + + Shortcut for openning the config files. + + * diff + + View a diff of the current file compared to it's on disk version. + + * eval + + Evaluate a python expression and show the result in the status bar. + + If no expression is provided the current line(s) are evaluated and + replaced with the evaluation result. + + * keymap + + Shortcut to openning the keymap config file. + + * linter + + Linter for suplemon. + + * lower + + Transform current lines to lower case. + + * lstrip + + Trim whitespace from beginning of current lines. + + * paste + + Toggle paste mode (helpful when pasting over SSH if auto indent is enabled) + + * reload + + Reload all add-on modules. + + * replace_all + + Replace all occurrences in all files of given text with given replacement. + + * reverse + + Reverse text on current line(s). + + * rstrip + + Trim whitespace from the end of lines. + + * save + + Save the current file. + + * save_all + + Save all currently open files. Asks for confirmation. + + * strip + + Trim whitespace from start and end of lines. + + * tabstospaces + + Convert tab characters to spaces in the entire file. + + * toggle_whitespace + + Toggle visually showing whitespace. + + * upper + + Transform current lines to upper case. + ## Support @@ -238,29 +334,6 @@ PRs are very welcome and appreciated. When making PRs make sure to set the target branch to `dev`. I only push to master when releasing new versions. -## Todo - * [ ] Design proper API for plugins/extensions/macros - * [ ] Documentation for v 1.0.0 - -## Wishlist (Stuff that would be nice, but not planning to do yet. *Maybe* for 2.0.0) - * [ ] Core - * [ ] Setting for enabling/disabling undo for cursor changes - * [ ] Selections - * [ ] List of recent files - * [X] Optionally Remember cursor positions in files (and restore when opened again) - * [ ] Read only viewer - * ~~And disable editing~~ Don't disable editing. Instead enable save as. - * [ ] Extensions: - * [ ] Peer to peer colaborative editing. Could be implemented as an extension. - * [ ] Auto backup. Activate on n changes or every n seconds - * [ ] File selector, kind of like what nano has - * [ ] This should be implemented as an extension - * [ ] Could be triggered with a key binding (and/or override open file) - * [ ] Need to refactor App class to support views instead of just files - * [ ] A view could be an editor or an extension ui - * [ ] Extensions should be able to control both status bars and key legend - - ## Rationale For many the command line is a different environment for text editing. Most coders are familiar with GUI text editors and for many vi and emacs diff --git a/suplemon/module_loader.py b/suplemon/module_loader.py index 5ee6e16..2441df7 100644 --- a/suplemon/module_loader.py +++ b/suplemon/module_loader.py @@ -87,7 +87,7 @@ def extract_docs(self): docstring = module["class"].__doc__ docstring = "\n " + docstring.strip() - doc = "- {0} \n{1}\n".format(name, docstring) + doc = " * {0}\n{1}\n".format(name, docstring) print(doc) From 98b26e458e5af3f7679ab449330f2694b168f18b Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sun, 17 Dec 2017 16:00:03 +0200 Subject: [PATCH 31/32] Update readme, changelog and help. --- CHANGELOG.md | 10 ++++ README.md | 18 ++++++++ suplemon/help.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9914b7..6e307f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ Change Log ========== +## [v0.1.64](https://github.com/richrd/suplemon/tree/v0.1.64) (2017-12-17) compared to previous master branch. +[Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.63...v0.1.64) + +**Implemented enhancements:** + +- Add bulk_delete and sort_lines commands. +- Lots of code style fixes and improvements. Credit @Gnewbee +- Add xclip support for system clipboard. Credit @LChris314 +- Added command docs to readme and help. + ## [v0.1.63](https://github.com/richrd/suplemon/tree/v0.1.63) (2017-10-05) compared to previous master branch. [Full Changelog](https://github.com/richrd/suplemon/compare/v0.1.62...v0.1.63) diff --git a/README.md b/README.md index bb90cd5..e9a7a20 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,16 @@ The available commands and their descriptions are: Function arguments are crudely parsed from the function definition and return statements are scanned from the function body. + * bulk_delete + + Bulk delete lines and characters. + Asks what direction to delete in by default. + + Add 'up' to delete lines above highest cursor. + Add 'down' to delete lines below lowest cursor. + Add 'left' to delete characters to the left of all cursors. + Add 'right' to delete characters to the right of all cursors. + * comment Toggle line commenting based on current file syntax. @@ -295,6 +305,14 @@ The available commands and their descriptions are: Save all currently open files. Asks for confirmation. + * sort_lines + + Sort current lines. + + Sorts alphabetically by default. + Add 'length' to sort by length. + Add 'reverse' to reverse the sorting. + * strip Trim whitespace from start and end of lines. diff --git a/suplemon/help.py b/suplemon/help.py index 2f4a1fb..ab69614 100644 --- a/suplemon/help.py +++ b/suplemon/help.py @@ -133,4 +133,120 @@ and then typing the command name. Commands are extensions and are stored in the modules folder in the Suplemon installation. + * autocomplete + + A simple autocompletion module. + + This adds autocomplete support for the tab key. It uses a word + list scanned from all open files for completions. By default it suggests + the shortest possible match. If there are no matches, the tab action is + run normally. + + * autodocstring + + Simple module for adding docstring placeholders. + + This module is intended to generate docstrings for Python functions. + It adds placeholders for descriptions, arguments and return data. + Function arguments are crudely parsed from the function definition + and return statements are scanned from the function body. + + * bulk_delete + + Bulk delete lines and characters. + Asks what direction to delete in by default. + + Add 'up' to delete lines above highest cursor. + Add 'down' to delete lines below lowest cursor. + Add 'left' to delete characters to the left of all cursors. + Add 'right' to delete characters to the right of all cursors. + + * comment + + Toggle line commenting based on current file syntax. + + * config + + Shortcut for openning the config files. + + * diff + + View a diff of the current file compared to it's on disk version. + + * eval + + Evaluate a python expression and show the result in the status bar. + + If no expression is provided the current line(s) are evaluated and + replaced with the evaluation result. + + * keymap + + Shortcut to openning the keymap config file. + + * linter + + Linter for suplemon. + + * lower + + Transform current lines to lower case. + + * lstrip + + Trim whitespace from beginning of current lines. + + * paste + + Toggle paste mode (helpful when pasting over SSH if auto indent is enabled) + + * reload + + Reload all add-on modules. + + * replace_all + + Replace all occurrences in all files of given text with given replacement. + + * reverse + + Reverse text on current line(s). + + * rstrip + + Trim whitespace from the end of lines. + + * save + + Save the current file. + + * save_all + + Save all currently open files. Asks for confirmation. + + * sort_lines + + Sort current lines. + + Sorts alphabetically by default. + Add 'length' to sort by length. + Add 'reverse' to reverse the sorting. + + * strip + + Trim whitespace from start and end of lines. + + * tabstospaces + + Convert tab characters to spaces in the entire file. + + * toggle_whitespace + + Toggle visually showing whitespace. + + * upper + + Transform current lines to upper case. + + """ From 12b812c4a72ab20e7011b0a39156337b80e1ea80 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Mon, 25 Dec 2017 15:11:24 +0200 Subject: [PATCH 32/32] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9a7a20..e129e49 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ http://github.com/richrd/suplemon * Find, Find next and Find all (Ctrl + F, Ctrl + D, Ctrl + A) * Custom keyboard shortcuts (and easy-to-use defaults) * Mouse support - * Restores cursor positions in when reopenning files + * Restores cursor and scroll positions when reopenning files * Extensions (easy to write your own) * Lots more...