From 9126abc6d59a3d3d4066a92fc0a6b71d20863ad0 Mon Sep 17 00:00:00 2001 From: spy Date: Wed, 20 Apr 2022 11:47:12 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Clean=20up,=20modularisation=20&=20?= =?UTF-8?q?refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++------ mode/TODO.md => TODO.md | 0 core/buffers.py | 39 ++++++++++++++++++++++++++++++--------- core/colors.py | 26 +++++++++++++------------- core/components.py | 2 +- core/utils.py | 38 ++++++++++++++++++++------------------ install.sh | 2 +- main.py | 40 ++++++++++++++++++++++++---------------- mode/insert.py | 2 +- 9 files changed, 101 insertions(+), 65 deletions(-) rename mode/TODO.md => TODO.md (100%) diff --git a/README.md b/README.md index 4df26d6..b760fd7 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,32 @@ # λ lambda + Next generation hackable text editor for nerds. ### Let it be known! + Lambda is in *very* early stages at the moment. Features may change completely, or even be removed.
Don't expect lambda to stay the way it is. Updates are pushed often. ### Overview + Lambda is a similar text editor to `vim` or `kakoune`.
However, it takes a different approach to most of the features seen in other editors. + - Lambda is written in Python, so it is easy to hack and learn. - - It also has a good amount of comments! + - It also has a good amount of comments! - Lambda is incredibly modular, so you can easily add new features. - Lambda follows the unix philosophy of "do one thing and do it well." - - It has no bloated features, like splits or tabs - - It contains the bare necessities and provides a few extra modules + - It has no bloated features, like splits or tabs + - It contains the bare necessities and provides a few extra modules - Lambda isn't limited to modes or keybindings. - - Keybindings and modes can be easily changed - - Other modes can be used by holding down keybindings (i.e. `ctrl-x` inside of `insert` mode) + - Keybindings and modes can be easily changed + - Other modes can be used by holding down keybindings (i.e. `ctrl-x` inside of `insert` mode) - Lambda is extremely fast and makes use of efficient memory management. - - Neovim is slow, and actually requires [a plugin to speed it up](https://github.com/lewis6991/impatient.nvim). + - Neovim is slow, and actually requires [a plugin to speed it up](https://github.com/lewis6991/impatient.nvim). - Lambda has much better default keybindings than other text editors. ### Getting started + ```bash git clone https://github.com/SpyHoodle/lambda.git # Clone the repository cd lambda # Enter lambda directory diff --git a/mode/TODO.md b/TODO.md similarity index 100% rename from mode/TODO.md rename to TODO.md diff --git a/core/buffers.py b/core/buffers.py index ef38f01..ad7279e 100644 --- a/core/buffers.py +++ b/core/buffers.py @@ -21,17 +21,38 @@ class Buffer: instance.components.components["left"]) + len(line), " " * (instance.safe_width - len(line))) @staticmethod - def remove_char(instance): - # Remove a character from a string at a given index - instance.buffer.data[instance.cursor[0]] = instance.buffer.data[instance.cursor[0]][:(instance.cursor[1] - 1)] \ - + instance.buffer.data[instance.cursor[0]][(instance.cursor[1] - 1) + 1:] + def delete_line(instance, y: int = None): + # Default to the cursor position + y = y or instance.cursor[0] + + # Remove a line from the buffer + instance.buffer.data.pop(y) @staticmethod - def insert_char(instance, char: (str, chr)): - # Insert a character into a string at a given index - instance.buffer.data[instance.cursor[0]] = instance.buffer.data[instance.cursor[0]][:instance.cursor[1]] + \ - char + \ - instance.buffer.data[instance.cursor[0]][instance.cursor[1]:] + def insert_line(instance, y: int = None): + # Default to the cursor position + y = y or instance.cursor[0] + + # Insert a line into the buffer + instance.buffer.data.insert(y, "") + + @staticmethod + def delete_char(instance, y: int = None, x: int = None): + # Default to the cursor position + y = y or instance.cursor[0] + x = x or instance.cursor[1] + + # Remove a character from the line at a given index + instance.buffer.data[y] = instance.buffer.data[y][:x - 1] + instance.buffer.data[y][x:] + + @staticmethod + def insert_char(instance, char: (str, chr), y: int = None, x: int = None): + # Default to the cursor position + y = y or instance.cursor[0] + x = x or instance.cursor[1] + + # Insert a character into the line at a given index + instance.buffer.data[y] = instance.buffer.data[y][:x] + char + instance.buffer.data[y][x:] def open_file(file_path): diff --git a/core/colors.py b/core/colors.py index 52ac799..1680042 100644 --- a/core/colors.py +++ b/core/colors.py @@ -22,30 +22,30 @@ def init_colors(): curses.start_color() # Foreground: WHITE, Background: BLACK - curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) # Foreground: BLACK, Background: WHITE - curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE) # Foreground: RED, Background: BLACK - curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) # Foreground: BLACK, Background: RED - curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_RED) + curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_RED) # Foreground: GREEN, Background: BLACK - curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) # Foreground: BLACK, Background: GREEN - curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_GREEN) + curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_GREEN) # Foreground: YELLOW, Background: BLACK - curses.init_pair(7, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(7, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Foreground: BLACK, Background: YELLOW - curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_YELLOW) + curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_YELLOW) # Foreground: CYAN, Background: BLACK - curses.init_pair(9, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(9, curses.COLOR_CYAN, curses.COLOR_BLACK) # Foreground: BLACK, Background: CYAN - curses.init_pair(10, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(10, curses.COLOR_BLACK, curses.COLOR_CYAN) # Foreground: BLUE, Background: BLACK - curses.init_pair(11, curses.COLOR_BLUE, curses.COLOR_BLACK) + curses.init_pair(11, curses.COLOR_BLUE, curses.COLOR_BLACK) # Foreground: BLACK, Background: BLUE - curses.init_pair(12, curses.COLOR_BLACK, curses.COLOR_BLUE) + curses.init_pair(12, curses.COLOR_BLACK, curses.COLOR_BLUE) # Foreground: MAGENTA, Background: BLACK curses.init_pair(13, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # Foreground: BLACK, Background: MAGENTA - curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA) + curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA) diff --git a/core/components.py b/core/components.py index c8af880..64c2ef0 100644 --- a/core/components.py +++ b/core/components.py @@ -18,7 +18,7 @@ class StatusBar: def render(self, instance): # Clear the status bar - utils.clear(instance, instance.height - 2, 0) + utils.clear_line(instance, instance.height - 2, 0) # Update variables self.update(instance) diff --git a/core/utils.py b/core/utils.py index f5f02a8..61ba8ef 100644 --- a/core/utils.py +++ b/core/utils.py @@ -17,12 +17,12 @@ def gracefully_exit(): sys.exit() -def clear(instance, y: int, x: int): +def clear_line(instance, y: int, x: int): # Clear the line at the screen at position y, x instance.screen.insstr(y, x, " " * (instance.width - x)) -def pause(message: str): +def pause_screen(message: str): # End the curses session curses.endwin() @@ -40,17 +40,22 @@ def save_file(instance, file_path: str, data: list): # Save the data to the file with open(file_path, "w") as f: try: + # For each line in the file for index, line in enumerate(data): if index == len(data) - 1: + # If this is the last line, write it without a newline f.write(line) + else: + # Otherwise, write the line with a newline f.write(f"{line}\n") - except Exception: + except (OSError, IOError): + # If the file could not be written, show an error message error(instance, f"File {file_path} could not be saved.") -def load_config() -> dict: +def load_config_file() -> dict: # Parse the path of the config file config_file_path = f"{Path.home()}/.config/lambda/config.json" @@ -60,10 +65,7 @@ def load_config() -> dict: return load_file(config_file_path) -def welcome(screen): - # Get window height and width - height, width = screen.getmaxyx() - +def welcome(instance): # Startup text title = "λ Lambda" subtext = [ @@ -75,17 +77,17 @@ def welcome(screen): ] # Centering calculations - start_x_title = int((width // 2) - (len(title) // 2) - len(title) % 2) - start_y = int((height // 2) - 2) + start_x_title = int((instance.safe_width // 2) - (len(title) // 2) - len(title) % 2 + 2) + start_y = int((instance.safe_height // 2) - 1) # Rendering title - screen.addstr(start_y, start_x_title, title, curses.color_pair(7) | curses.A_BOLD) + instance.screen.addstr(start_y, start_x_title, title, curses.color_pair(7) | curses.A_BOLD) # Print the subtext for text in subtext: start_y += 1 - start_x = int((width // 2) - (len(text) // 2) - len(text) % 2) - screen.addstr(start_y, start_x, text) + start_x = int((instance.safe_width // 2) - (len(text) // 2) - len(text) % 2 + 2) + instance.screen.addstr(start_y, start_x, text) def prompt(instance, message: str, color: int = 1) -> (list, None): @@ -93,7 +95,7 @@ def prompt(instance, message: str, color: int = 1) -> (list, None): inp = [] # Write whitespace over characters to refresh it - clear(instance, instance.height - 1, len(message) + len(inp) - 1) + clear_line(instance, instance.height - 1, len(message) + len(inp) - 1) # Write the message to the screen instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color)) @@ -105,7 +107,7 @@ def prompt(instance, message: str, color: int = 1) -> (list, None): # Subtracting a key (backspace) if key in (curses.KEY_BACKSPACE, 127, '\b'): # Write whitespace over characters to refresh it - clear(instance, instance.height - 1, len(message) + len(inp) - 1) + clear_line(instance, instance.height - 1, len(message) + len(inp) - 1) if inp: # Subtract a character from the input list @@ -148,7 +150,7 @@ def press_key_to_continue(instance, message: str, color: int = 1): cursors.mode("hidden") # Clear the bottom of the screen - clear(instance, instance.height - 1, 0) + clear_line(instance, instance.height - 1, 0) # Write the entire message to the screen instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color)) @@ -158,7 +160,7 @@ def press_key_to_continue(instance, message: str, color: int = 1): instance.screen.getch() # Clear the bottom of the screen - clear(instance, instance.height - 1, 0) + clear_line(instance, instance.height - 1, 0) # Show the cursor cursors.mode("visible") @@ -196,7 +198,7 @@ def goodbye(instance): # Clear the prompt if the user cancels else: - clear(instance, instance.height - 1, 0) + clear_line(instance, instance.height - 1, 0) except KeyboardInterrupt: # If the user presses Ctrl+C, just exit diff --git a/install.sh b/install.sh index 411b20a..0354a6e 100755 --- a/install.sh +++ b/install.sh @@ -6,6 +6,6 @@ cp -rf . ~/.local/share/lambda # Copy lambda launcher chmod +x ./lambda -rm -r ~/.local/bin/lambda +rm -rf ~/.local/bin/lambda ln -s ~/.local/share/lambda/lambda ~/.local/bin/lambda chmod +x ~/.local/bin/lambda \ No newline at end of file diff --git a/main.py b/main.py index df1fbc2..247bc62 100644 --- a/main.py +++ b/main.py @@ -62,25 +62,37 @@ class Lambda: # Show a welcome message if lambda opens with no file if not self.buffer.path: - utils.welcome(self.screen) + # Update the screen variables + self.refresh() + + # Show the welcome message + utils.welcome(self) # Main loop self.run() def run(self): - # The main loop, which runs until the user quits - while True: - # Update the screen variables - self.refresh() + try: + # The main loop, which runs until the user quits + while True: + # Update the screen variables + self.refresh() - # Wait for a keypress - key = self.screen.getch() + # Wait for a keypress + key = self.screen.getch() - # Handle the key - modes.handle_key(self, key) + # Handle the key + modes.handle_key(self, key) - # Refresh and clear the screen - self.screen.erase() + # Refresh and clear the screen + self.screen.erase() + + except KeyboardInterrupt: # Ctrl-C + # Create a goodbye prompt + utils.goodbye(self) + + # Run the main loop again + self.run() def main(): @@ -96,7 +108,7 @@ def main(): buffer = buffers.load_file(args.file) # Load the config - config = utils.load_config() + config = utils.load_config_file() # Load lambda with the buffer object instance = Lambda(buffer, config) @@ -105,10 +117,6 @@ def main(): try: instance.start() - # KeyboardInterrupt is thrown when is pressed (exit) - except KeyboardInterrupt: - utils.goodbye(instance) - # Excepts *any* errors that occur except Exception as exception: utils.fatal_error(exception) diff --git a/mode/insert.py b/mode/insert.py index 0dadc1d..2e7d9cc 100644 --- a/mode/insert.py +++ b/mode/insert.py @@ -11,7 +11,7 @@ def execute(instance, key): elif key in (curses.KEY_BACKSPACE, 127, '\b'): # Backspace if instance.cursor[1] > 0: # Delete the character before the cursor - instance.buffer.remove_char(instance) + instance.buffer.delete_char(instance) # Move the cursor one to the left cursors.push(instance, 3)