From ca0938b5239e2d508a5d3ad6a619ed946097ab8e Mon Sep 17 00:00:00 2001 From: Madeleine Date: Thu, 3 Nov 2022 07:31:24 +0000 Subject: [PATCH 01/16] rust branch --- .gitignore | 6 +- Cargo.lock | 7 ++ Cargo.toml | 8 ++ README.md | 35 -------- TODO.md | 1 - core/buffers.py | 90 ------------------- core/colors.py | 51 ----------- core/components.py | 73 ---------------- core/cursors.py | 68 --------------- core/modes.py | 34 -------- core/utils.py | 209 --------------------------------------------- install.sh | 11 --- lambda | 3 - main.py | 126 --------------------------- mode/command.py | 41 --------- mode/insert.py | 29 ------- mode/normal.py | 45 ---------- src/main.rs | 21 +++++ 18 files changed, 38 insertions(+), 820 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 README.md delete mode 100644 TODO.md delete mode 100644 core/buffers.py delete mode 100644 core/colors.py delete mode 100644 core/components.py delete mode 100644 core/cursors.py delete mode 100644 core/modes.py delete mode 100644 core/utils.py delete mode 100755 install.sh delete mode 100755 lambda delete mode 100644 main.py delete mode 100644 mode/command.py delete mode 100644 mode/insert.py delete mode 100644 mode/normal.py create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 9efad1c..ee44a96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -__pycache__ -core/__pycache__ -mode/__pycache__ -.idea \ No newline at end of file +.idea +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..568820c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "lambda" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..108e5c1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lambda" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md deleted file mode 100644 index b760fd7..0000000 --- a/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# λ 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! -- 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 -- 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) -- 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). -- 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 -chmod +x install.sh # Make the install script executable -./install.sh # Run the install script -``` \ No newline at end of file diff --git a/TODO.md b/TODO.md deleted file mode 100644 index a16511d..0000000 --- a/TODO.md +++ /dev/null @@ -1 +0,0 @@ -## TODO \ No newline at end of file diff --git a/core/buffers.py b/core/buffers.py deleted file mode 100644 index ad7279e..0000000 --- a/core/buffers.py +++ /dev/null @@ -1,90 +0,0 @@ -import os - - -class Buffer: - def __init__(self, path: str, name: str = None, data: list = None): - self.path = path - self.name = name or "[No Name]" - self.data = data or [""] - - def render(self, instance): - for y, line in enumerate(self.data[instance.offset[0]:]): - if y <= instance.safe_height: - for x, character in enumerate(line[instance.offset[1]:]): - if x <= instance.safe_width: - instance.screen.addstr(y, x + instance.components.get_component_width( - instance.components.components["left"]), character) - - # Write blank spaces for the rest of the line - if instance.safe_width - len(line) > 0: - instance.screen.addstr(y, instance.components.get_component_width( - instance.components.components["left"]) + len(line), " " * (instance.safe_width - len(line))) - - @staticmethod - 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_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): - # Open the file - with open(file_path) as f: - # Convert it into a list of lines - lines = f.readlines() - - # Add a line if the file is empty or if the last line is not empty - if lines[-1].endswith("\n") or not len(lines): - lines.append("") - - # Remove the newlines - lines = [line.rstrip("\n") for line in lines] - - # Return the list of lines - return lines - - -def load_file(file_path=None): - # Default settings for a file - file_name = "[No Name]" - file_data = [""] - - if file_path: - # Set the file's name - file_name = os.path.basename(file_path) - - # Only if the file actually exists - if os.path.exists(file_path): - # Open the file as a list of lines - file_data = open_file(file_path) - - # Return a dictionary which will become all the data about the buffer - return Buffer(file_path, file_name, file_data) diff --git a/core/colors.py b/core/colors.py deleted file mode 100644 index 1680042..0000000 --- a/core/colors.py +++ /dev/null @@ -1,51 +0,0 @@ -import curses - - -class Codes: - # Color codes - red = '\033[91m' - green = '\033[92m' - blue = '\033[94m' - yellow = '\033[93m' - cyan = '\033[96m' - magenta = '\033[95m' - white = '\033[97m' - selected_white = '\033[47m' - selected_green = '\033[42m' - strike = '\033[9m' - italic = '\033[3m' - end = '\033[0m' - - -def init_colors(): - # Activate color support - curses.start_color() - - # Foreground: WHITE, Background: 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) - # Foreground: RED, Background: 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) - # Foreground: GREEN, Background: 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) - # Foreground: YELLOW, Background: 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) - # Foreground: CYAN, Background: 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) - # Foreground: BLUE, Background: 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) - # 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) diff --git a/core/components.py b/core/components.py deleted file mode 100644 index 64c2ef0..0000000 --- a/core/components.py +++ /dev/null @@ -1,73 +0,0 @@ -import curses - -from core import utils - - -class StatusBar: - def __init__(self, instance): - self.mode = instance.mode.upper() - self.file = instance.buffer.name or "[No Name]" - self.icon = instance.config["icon"] or "λ" - self.theme = "default" - self.colors = [7, 5, 13] - self.components = [self.icon, self.mode, self.file] - - def update(self, instance): - self.mode = instance.mode.upper() - self.components = [self.icon, self.mode, self.file] - - def render(self, instance): - # Clear the status bar - utils.clear_line(instance, instance.height - 2, 0) - - # Update variables - self.update(instance) - - if self.theme == "inverted": - # Initialise the x position for each component - x = 1 - - # Render each component - for count, component in enumerate(self.components): - instance.screen.addstr(instance.height - 2, x, component, - curses.color_pair(self.colors[count]) | curses.A_BOLD) - x += len(component) + 1 - - else: - # Initialise temporary colors for inverted theme - colors = [] - - # Add 1 to each color temporarily - for color in self.colors: - colors.append(color + 1) - - # Initialise the x position for each component - x = 0 - - # Render each component - for count, component in enumerate(self.components): - component = f" {component} " - instance.screen.addstr(instance.height - 2, x, component, - curses.color_pair(colors[count]) | curses.A_BOLD) - x += len(component) - - # Add a space at the end of the status bar - instance.screen.addstr(instance.height - 2, x, " " * (instance.width - x), - curses.color_pair(2)) - - -class Components: - def __init__(self, instance, components: dict = None): - self.components = components or { - "left": [" "], - "bottom": [StatusBar(instance)], - } - curses.endwin() - - @staticmethod - def get_component_width(component: list) -> int: - return len(max(component)) - - def render(self, instance): - for component in self.components["bottom"]: - component.render(instance) diff --git a/core/cursors.py b/core/cursors.py deleted file mode 100644 index c76079f..0000000 --- a/core/cursors.py +++ /dev/null @@ -1,68 +0,0 @@ -import curses - - -def mode(to_mode: str): - if to_mode == "block": - print("\033[2 q") - - elif to_mode == "line": - print("\033[6 q") - - elif to_mode == "hidden": - curses.curs_set(0) - - elif to_mode == "visible": - curses.curs_set(1) - - -def push(instance, direction: (int, str)): - if direction in (0, "up", "north"): - # If the cursor isn't at the top of the screen - if instance.raw_cursor[0] > 0: - # Move the cursor up - instance.raw_cursor[0] -= 1 - - # Move the buffer upwards if the cursor is at the top of the screen and not at the top of the buffer - if instance.raw_cursor[0] == 0 and instance.cursor[0] == instance.offset[0] and instance.cursor[0] != 0: - instance.offset[0] -= 1 - - elif direction in (2, "down", "south"): - if instance.raw_cursor[0] == instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1: - instance.offset[0] += 1 - - # If the cursor isn't at the bottom of the screen - elif instance.raw_cursor[0] != instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1: - # Move the cursor down - instance.raw_cursor[0] += 1 - - elif direction in (1, "right", "east"): - # Move the cursor one to the right - instance.raw_cursor[1] += 1 - - elif direction in (3, "left", "west"): - # Move the cursor one to the left - instance.raw_cursor[1] -= 1 - - -def check(instance, cursor: list) -> list: - # Prevent the cursor from going outside the buffer - cursor[1] = min(len(instance.buffer.data[instance.cursor[0]]) - 1, cursor[1]) - - # Prevent any negative values - cursor[0] = max(0, cursor[0]) - cursor[1] = max(0, cursor[1]) - - # Prevent the cursor from going outside the screen - cursor[1] = min(instance.safe_width, cursor[1]) - cursor[0] = min(instance.safe_height, cursor[0]) - - return cursor - - -def move(instance): - # Run a final check to see if the cursor is valid - instance.raw_cursor = check(instance, instance.raw_cursor) - - # Moves the cursor to anywhere on the screen - instance.screen.move(instance.raw_cursor[0], instance.raw_cursor[1] + - instance.components.get_component_width(instance.components.components["left"])) diff --git a/core/modes.py b/core/modes.py deleted file mode 100644 index 04ae88c..0000000 --- a/core/modes.py +++ /dev/null @@ -1,34 +0,0 @@ -from mode import normal, insert, command - - -def activate(instance, mode): - # Visibly update the mode - instance.mode = mode - - # Refresh the screen - instance.refresh() - - if mode == "command": - # Activate command mode - instance.components.components["bottom"][0].colors[1] = 5 - command.activate(instance) - - elif mode == "insert": - # Activate insert mode - instance.components.components["bottom"][0].colors[1] = 11 - insert.activate() - - elif mode == "normal": - # Activate normal mode - instance.components.components["bottom"][0].colors[1] = 5 - normal.activate() - - -def handle_key(instance, key): - # Normal mode - default keybindings - if instance.mode == "normal": - normal.execute(instance, key) - - # Insert mode - inserting text to the buffer - elif instance.mode == "insert": - insert.execute(instance, key) diff --git a/core/utils.py b/core/utils.py deleted file mode 100644 index 61ba8ef..0000000 --- a/core/utils.py +++ /dev/null @@ -1,209 +0,0 @@ -import curses -import json -import os -import sys -import traceback -from pathlib import Path - -from core import cursors -from core.colors import Codes as Col - - -def gracefully_exit(): - # Close the curses window - curses.endwin() - - # Finally, exit the program - sys.exit() - - -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_screen(message: str): - # End the curses session - curses.endwin() - - # Print the message and wait for enter key - input(f"{message}\n\n Press enter to continue...") - - -def load_file(file_path: str) -> dict: - # load the json file with read permissions - with open(file_path, "r") as f: - return json.load(f) - - -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 (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_file() -> dict: - # Parse the path of the config file - config_file_path = f"{Path.home()}/.config/lambda/config.json" - - # Only if the config file exists, attempt to load it - if os.path.exists(config_file_path): - # Return the loaded config - return load_file(config_file_path) - - -def welcome(instance): - # Startup text - title = "λ Lambda" - subtext = [ - "Next generation hackable text editor for nerds", - "", - "Type :h to open the README.md document", - "Type :o to open a file and edit", - "Type :q or to quit lambda.py" - ] - - # Centering calculations - 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 - 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((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): - # Initialise the input list - inp = [] - - # Write whitespace over characters to refresh it - 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)) - - while True: - # Wait for a keypress - key = instance.screen.getch() - - # Subtracting a key (backspace) - if key in (curses.KEY_BACKSPACE, 127, '\b'): - # Write whitespace over characters to refresh it - clear_line(instance, instance.height - 1, len(message) + len(inp) - 1) - - if inp: - # Subtract a character from the input list - inp.pop() - - else: - # Exit the prompt without returning the input - return None - - elif key == 27: - # Exit the prompt, without returning the input - return None - - elif key in (curses.KEY_ENTER, ord('\n'), ord('\r'), ord(":"), ord(";")): - # Return the input list - return inp - - else: - # If any other key is typed, append it - # As long as the key is in the valid list - valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!/_-0123456789 " - if chr(key) in valid and len(inp) < (instance.width - 2): - inp.append(chr(key)) - - # Refresh the screen - instance.refresh() - - # Write the message to the screen - instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color)) - - # Join the input together for visibility on the screen - input_text = "".join(inp) - - # Write the input text to the screen - instance.screen.addstr(instance.height - 1, len(message), input_text) - - -def press_key_to_continue(instance, message: str, color: int = 1): - # Hide the cursor - cursors.mode("hidden") - - # Clear the bottom of the screen - 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)) - instance.screen.addstr(instance.height - 1, len(message) + 1, f"(press any key)") - - # Wait for a keypress - instance.screen.getch() - - # Clear the bottom of the screen - clear_line(instance, instance.height - 1, 0) - - # Show the cursor - cursors.mode("visible") - - -def error(instance, message: str): - # Parse the error message - error_message = f"ERROR: {message}" - - # Create a prompt - press_key_to_continue(instance, error_message, 3) - - -def fatal_error(exception: Exception): - # End the curses session - curses.endwin() - - # Print the error message and traceback - print(f"{Col.red}FATAL ERROR:{Col.end} " - f"{Col.yellow}{exception}{Col.end}\n") - print(traceback.format_exc()) - - # Exit, with an error exit code - sys.exit(0) - - -def goodbye(instance): - try: - # Confirm before exiting - choice = prompt(instance, "Really quit lambda? (y/n): ", 11) - - # If the user confirms, exit - if choice and choice[0] == "y": - gracefully_exit() - - # Clear the prompt if the user cancels - else: - clear_line(instance, instance.height - 1, 0) - - except KeyboardInterrupt: - # If the user presses Ctrl+C, just exit - gracefully_exit() - - except Exception as exception: - # If there is an error, print the error message and traceback - fatal_error(exception) diff --git a/install.sh b/install.sh deleted file mode 100755 index 0354a6e..0000000 --- a/install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -# Copy lambda to ~/.local/share -mkdir -p ~/.local/share/lambda -cp -rf . ~/.local/share/lambda - -# Copy lambda launcher -chmod +x ./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/lambda b/lambda deleted file mode 100755 index 7c1d1e5..0000000 --- a/lambda +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python3 ~/.local/share/lambda/main.py "$@" diff --git a/main.py b/main.py deleted file mode 100644 index 247bc62..0000000 --- a/main.py +++ /dev/null @@ -1,126 +0,0 @@ -import argparse -import curses -import os - -from core import colors, cursors, buffers, modes, utils -from core.buffers import Buffer -from core.components import Components - - -class Lambda: - def __init__(self, buffer: Buffer = None, config: dict = None): - self.cursor = [0, 0] - self.raw_cursor = [0, 0] - self.offset = [0, 0] - self.height = 0 - self.width = 0 - self.safe_height = 0 - self.safe_width = 0 - self.mode = "normal" - self.config = config or {"icon": "λ"} - self.buffer = buffer or [""] - self.screen = curses.initscr() - self.components = Components(self) - - def update_dimensions(self): - # Calculate the entire height and width of the terminal - self.height, self.width = self.screen.getmaxyx() - - # Calculate the safe area for the buffer by removing heights & widths of components - self.safe_height = self.height - len(self.components.components["bottom"]) - 2 - self.safe_width = self.width - self.components.get_component_width(self.components.components["left"]) - 1 - - def refresh(self): - # Calculate the cursor position in the file - self.cursor[0], self.cursor[1] = self.raw_cursor[0] + self.offset[0], self.raw_cursor[1] + self.offset[1] - - # Update the dimensions of the terminal - self.update_dimensions() - - # Write the buffer to the screen - self.buffer.render(self) - - # Refresh the on-screen components - self.components.render(self) - - # Move the cursor - cursors.move(self) - - def start(self): - # Change the escape key delay to 25ms - # Fixes an issue where the "esc" key takes way too long to press - os.environ.setdefault("ESCDELAY", "25") - - # Initialise colors - colors.init_colors() - - # Change the cursor shape - cursors.mode("block") - - # Don't echo any key-presses - curses.noecho() - - # Show a welcome message if lambda opens with no file - if not self.buffer.path: - # Update the screen variables - self.refresh() - - # Show the welcome message - utils.welcome(self) - - # Main loop - self.run() - - def run(self): - 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() - - # Handle the key - modes.handle_key(self, key) - - # 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(): - # Shell arguments - parser = argparse.ArgumentParser(description="Next generation hackable text editor for nerds.") - parser.add_argument("file", metavar="file", type=str, nargs="?", - help="The name of a file for lambda to open") - - # Collect the arguments passed into lambda at the shell - args = parser.parse_args() - - # Load the file into a Buffer object - buffer = buffers.load_file(args.file) - - # Load the config - config = utils.load_config_file() - - # Load lambda with the buffer object - instance = Lambda(buffer, config) - - # Start the screen, this will loop until exit - try: - instance.start() - - # Excepts *any* errors that occur - except Exception as exception: - utils.fatal_error(exception) - - -if __name__ == "__main__": - main() diff --git a/mode/command.py b/mode/command.py deleted file mode 100644 index 51d28e8..0000000 --- a/mode/command.py +++ /dev/null @@ -1,41 +0,0 @@ -from core import utils - - -def execute(instance, commands: list): - # Only if commands are given - if commands: - # Check each command in the list of commands - for command in commands: - if command == "d": # Debug - # Create the debug prompt - utils.press_key_to_continue(instance, f"Cursor: {instance.cursor} Raw: {instance.raw_cursor} " - f"Len: {len(instance.buffer.data)}") - - elif command == "t": # Toggle - # Toggle the status bar theme - if instance.components.components["bottom"][0].theme == "default": - instance.components.components["bottom"][0].theme = "inverted" - else: - instance.components.components["bottom"][0].theme = "default" - - elif command == "w": # Write - # Write to the file - utils.save_file(instance, instance.buffer.path, instance.buffer.data) - - elif command == "q": # Quit - # Create a goodbye prompt - utils.goodbye(instance) - - else: # Invalid command - utils.error(instance, f"invalid command: '{command}'") - - -def activate(instance): - # Create a prompt, which returns the input (commands) - commands = utils.prompt(instance, ":") - - # Execute the commands given - execute(instance, commands) - - # Return to normal mode once all commands are executed - instance.mode = "normal" diff --git a/mode/insert.py b/mode/insert.py deleted file mode 100644 index 2e7d9cc..0000000 --- a/mode/insert.py +++ /dev/null @@ -1,29 +0,0 @@ -import curses - -from core import cursors, modes - - -def execute(instance, key): - if key == 27: # Escape - # Switch to normal mode - modes.activate(instance, "normal") - - elif key in (curses.KEY_BACKSPACE, 127, '\b'): # Backspace - if instance.cursor[1] > 0: - # Delete the character before the cursor - instance.buffer.delete_char(instance) - - # Move the cursor one to the left - cursors.push(instance, 3) - - else: - # Insert the character - instance.buffer.insert_char(instance, chr(key)) - - # Move the cursor one to the right - cursors.push(instance, 1) - - -def activate(): - # Switch the cursor to a line - cursors.mode("line") diff --git a/mode/normal.py b/mode/normal.py deleted file mode 100644 index 4bc0d44..0000000 --- a/mode/normal.py +++ /dev/null @@ -1,45 +0,0 @@ -import curses - -from core import cursors, modes, utils - - -def execute(instance, key): - if key == curses.BUTTON1_CLICKED: - # Move the cursor to the position clicked - utils.prompt(instance, str(curses.getmouse())) - - elif key in (ord("j"), curses.KEY_DOWN): - # Move the cursor down - cursors.push(instance, "down") - - elif key in (ord("k"), curses.KEY_UP): - # Move the cursor up - cursors.push(instance, "up") - - elif key in (ord("l"), curses.KEY_RIGHT): - # Move the cursor right - cursors.push(instance, "right") - - elif key in (ord("h"), curses.KEY_LEFT): - # Move the cursor left - cursors.push(instance, "left") - - elif key == ord("i"): - # Activate insert mode - modes.activate(instance, "insert") - - elif key == ord("I"): - # Move the cursor to the right - cursors.push(instance, "right") - - # Then activate insert mode - modes.activate(instance, "insert") - - elif key in (ord(":"), ord(";")): - # Activate command mode - modes.activate(instance, "command") - - -def activate(): - # Switch the cursor to a block - cursors.mode("block") diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2fe3baf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,21 @@ +struct Buffer { + data: Vec, + name: &str, +} + +struct Editor { + buffer: Buffer, +} + +fn main() { + let bufffer = Buffer { + data: Vec::from([String::from("a test")]), + name: "uhh" + }; + + let editor = Editor { + buffer: bufffer, + }; + + println!("{}", editor.buffer.data[0]) +} From 70678df036f3420525bf1a55fb05e78d9b7b0fe7 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Thu, 3 Nov 2022 08:47:26 +0000 Subject: [PATCH 02/16] extending structs --- src/main.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2fe3baf..8f0a552 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,32 @@ -struct Buffer { +enum Mode { + Normal, + Insert, + Select +} + +struct Buffer<'a> { data: Vec, - name: &str, + name: &'a str, + path: &'a str, } struct Editor { buffer: Buffer, + cursor: [i32, 2], + mode: Mode; } fn main() { let bufffer = Buffer { - data: Vec::from([String::from("a test")]), - name: "uhh" + data: Vec::from([String::from("Hello"), String::from("World")]), + name: "[No Name]", + path: "/home/spy", }; let editor = Editor { buffer: bufffer, + cursor: [0, 0], + mode: Mode::Normal, }; println!("{}", editor.buffer.data[0]) From 94d5d6f8d4bb58f47317078b0f166b5ae996c1f1 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Thu, 3 Nov 2022 13:33:00 +0000 Subject: [PATCH 03/16] fixed + added repl config --- .replit | 1 + src/main.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .replit diff --git a/.replit b/.replit new file mode 100644 index 0000000..6074353 --- /dev/null +++ b/.replit @@ -0,0 +1 @@ +run = "cargo run" diff --git a/src/main.rs b/src/main.rs index 8f0a552..7449827 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,10 +10,10 @@ struct Buffer<'a> { path: &'a str, } -struct Editor { - buffer: Buffer, - cursor: [i32, 2], - mode: Mode; +struct Editor<'a> { + buffer: Buffer<'a>, + cursor: [i32; 2], + mode: Mode, } fn main() { From 9b523dbd06cb917697194e5a1a6c1b4a408b7d9f Mon Sep 17 00:00:00 2001 From: Madeleine Date: Thu, 3 Nov 2022 20:20:13 +0000 Subject: [PATCH 04/16] building up core and terminal --- Cargo.lock | 242 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/editor.rs | 31 +++++++ src/main.rs | 37 ++------ src/terminal.rs | 39 ++++++++ 5 files changed, 320 insertions(+), 32 deletions(-) create mode 100644 src/editor.rs create mode 100644 src/terminal.rs diff --git a/Cargo.lock b/Cargo.lock index 568820c..aa77f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,248 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + [[package]] name = "lambda" version = "0.1.0" +dependencies = [ + "crossterm", +] + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index 108e5c1..23443e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,5 @@ name = "lambda" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +crossterm = "0.25" diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000..e82877d --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,31 @@ +enum Mode { + Normal, + Insert, + Select +} + +pub struct Buffer<'a> { + pub data: Vec, + pub name: &'a str, + pub path: &'a str, +} + +pub struct Editor<'a> { + pub buffer: Buffer<'a>, + pub cursor: [i32; 2], + mode: Mode, +} + +impl<'a> Editor<'a> { + pub fn new() -> Self { + Editor { + buffer: Buffer { + data: Vec::from([String::from("Hello"), String::from("World")]), + name: "[No Name]", + path: "/home/spy", + }, + cursor: [0, 0], + mode: Mode::Normal, + } + } +} diff --git a/src/main.rs b/src/main.rs index 7449827..bb0797e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,10 @@ -enum Mode { - Normal, - Insert, - Select -} - -struct Buffer<'a> { - data: Vec, - name: &'a str, - path: &'a str, -} - -struct Editor<'a> { - buffer: Buffer<'a>, - cursor: [i32; 2], - mode: Mode, -} +mod terminal; +mod editor; fn main() { - let bufffer = Buffer { - data: Vec::from([String::from("Hello"), String::from("World")]), - name: "[No Name]", - path: "/home/spy", - }; - - let editor = Editor { - buffer: bufffer, - cursor: [0, 0], - mode: Mode::Normal, - }; - - println!("{}", editor.buffer.data[0]) + let lambda = editor::Editor::new(); + println!("{}", lambda.buffer.data[0]); + let term = terminal::Terminal::new(); + println!("{:?}", term); + terminal::Terminal::exit(); } diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 0000000..da2fa3a --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,39 @@ +use crossterm::terminal; +use crossterm::{execute, ErrorKind}; +use std::io::{stdout, Write}; + +#[derive(Debug)] +pub struct Size { + pub width: usize, + pub height: usize, +} + +#[derive(Debug)] +pub struct Terminal { + pub size: Size, +} + +impl Terminal { + pub fn new() -> Result { + let size = terminal::size()?; + Terminal::enter(); + Ok(Self { + size: Size { + width: size.0 as usize, + height: size.1 as usize, + }, + }) + } + + pub fn enter() { + // Enter the current terminal + terminal::enable_raw_mode().unwrap(); + execute!(stdout(), terminal::EnterAlternateScreen).unwrap(); + } + + pub fn exit() { + // Exit the current terminal + execute!(stdout(), terminal::LeaveAlternateScreen).unwrap(); + terminal::disable_raw_mode().unwrap(); + } +} From 62e9bd45e5b9d0abb5e29feeb394bd54782bb1cc Mon Sep 17 00:00:00 2001 From: Madeleine Date: Thu, 3 Nov 2022 20:29:08 +0000 Subject: [PATCH 05/16] some testing --- src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index bb0797e..b9683b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + mod terminal; mod editor; @@ -5,6 +7,9 @@ fn main() { let lambda = editor::Editor::new(); println!("{}", lambda.buffer.data[0]); let term = terminal::Terminal::new(); - println!("{:?}", term); - terminal::Terminal::exit(); + for _ in [0..1000000000] { + println!("{:?}", term); + std::thread::sleep(Duration::from_millis(2000)); + }; + terminal::Terminal::exit() } From 9a9c14bc0f7d78614fda39d74e2788e4f82746b9 Mon Sep 17 00:00:00 2001 From: Madeleine Date: Fri, 4 Nov 2022 07:19:20 +0000 Subject: [PATCH 06/16] some more testing --- src/main.rs | 13 ++++++++----- src/terminal.rs | 5 +++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index b9683b0..8c475ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,14 @@ mod editor; fn main() { let lambda = editor::Editor::new(); - println!("{}", lambda.buffer.data[0]); - let term = terminal::Terminal::new(); - for _ in [0..1000000000] { - println!("{:?}", term); - std::thread::sleep(Duration::from_millis(2000)); + let _term = terminal::Terminal::new(); + loop { + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + for line in lambda.buffer.data { + terminal::Terminal::write(format!("{line}")); + }; + std::thread::sleep(Duration::from_millis(3000)); + break; }; terminal::Terminal::exit() } diff --git a/src/terminal.rs b/src/terminal.rs index da2fa3a..a4a4f9b 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,5 +1,6 @@ use crossterm::terminal; use crossterm::{execute, ErrorKind}; +use crossterm::style::Print; use std::io::{stdout, Write}; #[derive(Debug)] @@ -36,4 +37,8 @@ impl Terminal { execute!(stdout(), terminal::LeaveAlternateScreen).unwrap(); terminal::disable_raw_mode().unwrap(); } + + pub fn write(text: String) { + execute!(stdout(), Print(text)).unwrap(); + } } From 09d26ec97e53b30e83c127a2d2207d621ca7bbe0 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Sat, 5 Nov 2022 09:57:41 +0000 Subject: [PATCH 07/16] more struct building + testing --- src/main.rs | 8 ++++---- src/terminal.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8c475ee..b1db43d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,14 +5,14 @@ mod editor; fn main() { let lambda = editor::Editor::new(); - let _term = terminal::Terminal::new(); + let mut term = terminal::Terminal::new().unwrap(); loop { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); for line in lambda.buffer.data { terminal::Terminal::write(format!("{line}")); + mut term.cursor.move_to(0, 1); }; - std::thread::sleep(Duration::from_millis(3000)); + std::thread::sleep(Duration::from_secs(3)); break; }; - terminal::Terminal::exit() + terminal::Terminal::exit(); } diff --git a/src/terminal.rs b/src/terminal.rs index a4a4f9b..81e5045 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,28 +1,65 @@ use crossterm::terminal; use crossterm::{execute, ErrorKind}; use crossterm::style::Print; +use crossterm::cursor::{CursorShape, MoveTo}; use std::io::{stdout, Write}; -#[derive(Debug)] pub struct Size { pub width: usize, pub height: usize, } -#[derive(Debug)] +pub struct Coords { + pub x: usize, + pub y: usize, +} + +impl Coords { + pub fn from(x: usize, y: usize) -> Self { + Self { + x: 0, + y: 0, + } + } +} + +pub struct Cursor { + pub position: Coords, + pub shape: CursorShape, +} + +impl Cursor { + pub fn new() -> Result { + let cursor = Self { + position: Coords::from(0, 0), + shape: CursorShape::Block, + }; + Cursor::move_to(&mut cursor, 0, 0); + Ok(cursor) + } + + pub fn move_to(&mut self, x: u16, y: u16) { + self.position = Coords::from(x as usize, y as usize); + execute!(stdout(), MoveTo(x, y)).unwrap() + } +} + pub struct Terminal { pub size: Size, + pub cursor: Cursor, } impl Terminal { pub fn new() -> Result { let size = terminal::size()?; + Terminal::clear(); Terminal::enter(); Ok(Self { size: Size { width: size.0 as usize, height: size.1 as usize, }, + cursor: Cursor::new().unwrap(), }) } @@ -39,6 +76,12 @@ impl Terminal { } pub fn write(text: String) { + // Writes a line to a current cursor position execute!(stdout(), Print(text)).unwrap(); } + + pub fn clear() { + // Clears the terminal screen + execute!(stdout(), terminal::Clear(terminal::ClearType::All)).unwrap(); + } } From a8a978d86e42a02da772e99ee1fb6bc5392c86ff Mon Sep 17 00:00:00 2001 From: Madeleine Date: Sun, 6 Nov 2022 18:35:05 +0000 Subject: [PATCH 08/16] built more of the editor + tui parts --- Cargo.lock | 61 +++++++++++++++++++++++++ Cargo.toml | 2 + src/editor.rs | 38 +++++++++++++--- src/main.rs | 17 ++----- src/terminal.rs | 118 +++++++++++++++++++++++++++++++++++------------- src/tui.rs | 56 +++++++++++++++++++++++ 6 files changed, 240 insertions(+), 52 deletions(-) create mode 100644 src/tui.rs diff --git a/Cargo.lock b/Cargo.lock index aa77f7c..2b775d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -20,6 +31,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "crossterm" version = "0.25.0" @@ -45,13 +67,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "lambda" version = "0.1.0" dependencies = [ + "colored", "crossterm", + "ctrlc", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.137" @@ -89,6 +138,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "parking_lot" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 23443e5..1a9392f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] crossterm = "0.25" +colored = "2" +ctrlc = "3.2.3" diff --git a/src/editor.rs b/src/editor.rs index e82877d..bc2f5d7 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,7 +1,11 @@ -enum Mode { - Normal, - Insert, - Select +pub struct Config<'a> { + pub logo: &'a str, +} + +impl<'a> Config<'a> { + pub fn new() -> Self { + Self { logo: "λ" } + } } pub struct Buffer<'a> { @@ -10,21 +14,41 @@ pub struct Buffer<'a> { pub path: &'a str, } +pub enum Mode { + Normal, + Insert, + Select, + Command, +} + +impl Mode { + pub fn as_str(&self) -> &str { + match self { + Mode::Normal => "NORMAL", + Mode::Insert => "INSERT", + Mode::Select => "SELECT", + Mode::Command => "COMMAND", + } + } +} + pub struct Editor<'a> { + pub config: Config<'a>, pub buffer: Buffer<'a>, - pub cursor: [i32; 2], - mode: Mode, + pub cursors: Vec, + pub mode: Mode, } impl<'a> Editor<'a> { pub fn new() -> Self { Editor { + config: Config::new(), buffer: Buffer { data: Vec::from([String::from("Hello"), String::from("World")]), name: "[No Name]", path: "/home/spy", }, - cursor: [0, 0], + cursors: Vec::from([0]), mode: Mode::Normal, } } diff --git a/src/main.rs b/src/main.rs index b1db43d..6e25b09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,9 @@ -use std::time::Duration; - -mod terminal; mod editor; +mod terminal; +mod tui; fn main() { let lambda = editor::Editor::new(); - let mut term = terminal::Terminal::new().unwrap(); - loop { - for line in lambda.buffer.data { - terminal::Terminal::write(format!("{line}")); - mut term.cursor.move_to(0, 1); - }; - std::thread::sleep(Duration::from_secs(3)); - break; - }; - terminal::Terminal::exit(); + let mut screen = terminal::Screen::new().unwrap(); + tui::start(&mut screen, lambda); } diff --git a/src/terminal.rs b/src/terminal.rs index 81e5045..d1f7dc3 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,76 +1,124 @@ +use crossterm::cursor::{Hide, MoveTo, Show}; +use crossterm::style::Print; use crossterm::terminal; use crossterm::{execute, ErrorKind}; -use crossterm::style::Print; -use crossterm::cursor::{CursorShape, MoveTo}; -use std::io::{stdout, Write}; - -pub struct Size { - pub width: usize, - pub height: usize, -} +use std::io::stdout; +// Struct for holding coordinates +#[derive(Copy, Clone)] pub struct Coords { pub x: usize, pub y: usize, } +// Creating a coordinates from two values impl Coords { pub fn from(x: usize, y: usize) -> Self { - Self { - x: 0, - y: 0, - } + Self { x, y } } } +// A cursor for writing to the terminal screen +#[derive(Copy, Clone)] pub struct Cursor { pub position: Coords, - pub shape: CursorShape, + pub hidden: bool, } +// When a cursor is created impl Cursor { pub fn new() -> Result { - let cursor = Self { + let mut cursor = Self { position: Coords::from(0, 0), - shape: CursorShape::Block, + hidden: true, }; - Cursor::move_to(&mut cursor, 0, 0); + Cursor::move_to(&mut cursor, Coords::from(0, 0)); + Cursor::hide(&mut cursor); Ok(cursor) } - - pub fn move_to(&mut self, x: u16, y: u16) { - self.position = Coords::from(x as usize, y as usize); - execute!(stdout(), MoveTo(x, y)).unwrap() - } } -pub struct Terminal { +// Cursor methods +impl Cursor { + pub fn move_to(&mut self, position: Coords) { + // Set the new position of the cursor + self.position = position; + + // Move the cursor to the desired posiition in the terminal + execute!(stdout(), MoveTo(position.x as u16, position.y as u16)).unwrap(); + } + + pub fn hide(&mut self) { + // Remember that the cursor is hidden + self.hidden = true; + + // Hide the cursor from the terminal screen + execute!(stdout(), Hide).unwrap(); + } + + pub fn show(&mut self) { + // Remember that the cursor isn't hidden + self.hidden = false; + + // Show the cursor to the terminal screen + execute!(stdout(), Show).unwrap(); + } +} + +// A struct for holding the size of the terminal +pub struct Size { + pub width: usize, + pub height: usize, +} + +// The terminal screen +pub struct Screen { pub size: Size, pub cursor: Cursor, } -impl Terminal { +// For when a new terminal screen is created +impl Screen { pub fn new() -> Result { + // Get the size of the terminal let size = terminal::size()?; - Terminal::clear(); - Terminal::enter(); - Ok(Self { + + // Define a new terminal screen struct + let mut screen = Self { size: Size { width: size.0 as usize, height: size.1 as usize, }, cursor: Cursor::new().unwrap(), - }) - } + }; - pub fn enter() { - // Enter the current terminal + // Empty the terminal screen + Screen::clear(); + + // Enter the terminal screen + screen.enter(); + + // Return a result containing the terminal + Ok(screen) + } +} + +// Terminal functions and methods for managing the terminal +impl Screen { + pub fn enter(&mut self) { + // Hide the cursor + self.cursor.hide(); + + // Enter the terminal screen terminal::enable_raw_mode().unwrap(); execute!(stdout(), terminal::EnterAlternateScreen).unwrap(); } - pub fn exit() { - // Exit the current terminal + pub fn exit(&mut self) { + // Show the cursor + self.cursor.show(); + + // Exit the terminal screen execute!(stdout(), terminal::LeaveAlternateScreen).unwrap(); terminal::disable_raw_mode().unwrap(); } @@ -84,4 +132,10 @@ impl Terminal { // Clears the terminal screen execute!(stdout(), terminal::Clear(terminal::ClearType::All)).unwrap(); } + + pub fn write_at(&mut self, text: String, position: Coords) { + // Writes a line at a set of coordinates + self.cursor.move_to(position); + Screen::write(text); + } } diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..79ee749 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,56 @@ +use crate::editor::Editor; +use crate::terminal::{Coords, Screen}; +use colored::Colorize; +use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; + +pub fn draw_status(screen: &mut Screen, editor: &Editor) { + // Calculate where to draw the status bar + let status_height = screen.size.height - 2; + + // Get the editor logo from the config + let editor_logo = &format!(" {} ", editor.config.logo) as &str; + // Write the editor logo + screen.write_at( + editor_logo.bright_yellow().bold().reversed().to_string(), + Coords::from(0, status_height), + ); + + // Get the current mode into a string + let mode_string = &format!(" {} ", editor.mode.as_str()) as &str; + // Calculate where to write the current mode + let x = editor_logo.len() - 1; + // Write the current mode + screen.write_at( + mode_string.green().bold().reversed().to_string(), + Coords::from(x, status_height), + ); + + // Get the current open file name + let file_name = &format!(" {} ", editor.buffer.name) as &str; + // Calculate where to write the file name + let x = editor_logo.len() + mode_string.len() - 1; + // Write the current file name + screen.write_at( + file_name.magenta().bold().reversed().to_string(), + Coords::from(x, status_height), + ); +} + +pub fn start(screen: &mut Screen, editor: Editor) { + loop { + // Draw the status bar + draw_status(screen, &editor); + + // Check for any key presses + match read().unwrap() { + Event::Key(KeyEvent { + code: KeyCode::Char('q'), + modifiers: KeyModifiers::CONTROL, + .. + }) => break, + _ => (), + } + } + + screen.exit(); +} From 280e569e92ae21e1c49af756fd17774fb95b83b0 Mon Sep 17 00:00:00 2001 From: Madeleine Date: Mon, 7 Nov 2022 06:52:11 +0000 Subject: [PATCH 09/16] more statusbar, keybind & cargo.toml ammendment --- Cargo.lock | 23 ----------------------- Cargo.toml | 1 - TODO.md | 1 + src/tui.rs | 14 +++++++++++++- 4 files changed, 14 insertions(+), 25 deletions(-) create mode 100644 TODO.md diff --git a/Cargo.lock b/Cargo.lock index 2b775d9..943f46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,16 +67,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ctrlc" -version = "3.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" -dependencies = [ - "nix", - "winapi", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -92,7 +82,6 @@ version = "0.1.0" dependencies = [ "colored", "crossterm", - "ctrlc", ] [[package]] @@ -138,18 +127,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "nix" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "parking_lot" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 1a9392f..43281cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,3 @@ edition = "2021" [dependencies] crossterm = "0.25" colored = "2" -ctrlc = "3.2.3" diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b3646b2 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- [ ] Remove colored in favour of crossterm's styling diff --git a/src/tui.rs b/src/tui.rs index 79ee749..538e813 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -28,12 +28,19 @@ pub fn draw_status(screen: &mut Screen, editor: &Editor) { // Get the current open file name let file_name = &format!(" {} ", editor.buffer.name) as &str; // Calculate where to write the file name - let x = editor_logo.len() + mode_string.len() - 1; + let x = x + mode_string.len(); // Write the current file name screen.write_at( file_name.magenta().bold().reversed().to_string(), Coords::from(x, status_height), ); + + // Draw the rest of the status bar + let x = x + file_name.len(); + screen.write_at( + " ".repeat(screen.size.width - x).reversed().to_string(), + Coords::from(x, status_height), + ); } pub fn start(screen: &mut Screen, editor: Editor) { @@ -48,6 +55,11 @@ pub fn start(screen: &mut Screen, editor: Editor) { modifiers: KeyModifiers::CONTROL, .. }) => break, + Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) => break, _ => (), } } From a9a0931d1f5aeaec937d400e72bd89d0e7de3952 Mon Sep 17 00:00:00 2001 From: Madeleine Date: Mon, 7 Nov 2022 07:30:09 +0000 Subject: [PATCH 10/16] added a README --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8cc4e9 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# λ Lambda +A next-generation hackable incredibly performant rust text editor for nerds. +> ⚠️ Lambda is in *very* early stages at the moment. Lambda's goals are still being decided and features may completely change. + +## Overview +Lambda is a similar text editor to `vim` or `kakoune`, taking some ideas from `xi`. +The main goal is to build the best text editor possible by taking ideas from existing text editors and implementing them in the best possible way. + +- Lambda is written in Rust, so it's incredibly fast and logical + - It's also full of comments, so anyone can try and learn what it's doing +- Lambda is very modular, so features can be easily added +- 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 +- Lambda has much better default keybindings than other text editors + +## Getting started +You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`. +``` +git clone https://github.com/SpyHoodle/lambda.git # Clone the repositiory +cargo run # Build and run lambda! +``` From b5cd240ee66fb4f79a059501972b9651e52aa5d6 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Mon, 7 Nov 2022 07:32:03 +0000 Subject: [PATCH 11/16] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8cc4e9..0f2d1b6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The main goal is to build the best text editor possible by taking ideas from exi ## Getting started You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`. -``` +```bash git clone https://github.com/SpyHoodle/lambda.git # Clone the repositiory cargo run # Build and run lambda! ``` From bd5ae72b6a3a83ef087f4d743074a945838def04 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:11:06 +0000 Subject: [PATCH 12/16] added a welcome message --- src/tui.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/tui.rs b/src/tui.rs index 538e813..60ca480 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -43,8 +43,37 @@ pub fn draw_status(screen: &mut Screen, editor: &Editor) { ); } +pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { + // The welcome message + let message: [&str; 6] = [ + &"λ Lambda".bright_yellow() as &str, + "Hackable text editor for nerds", + "", + "Type :help to open the README.md document", + "Type :o to open a file and edit", + "Type :q! or to quit lambda", + ]; + + // The starting y position in the centre of the screen + let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; + + for line in message { + // Each line has different width so requires a different x position to center it + let x = (screen.size.width / 2) - (line.len() / 2); + + // For each line we move downwards so increment y + y += 1; + + // Write the line to the screen at position (x, y) + screen.write_at(line.to_string(), Coords::from(x, y)); + } +} + pub fn start(screen: &mut Screen, editor: Editor) { loop { + // Draw the welcome message + draw_welcome(screen, &editor); + // Draw the status bar draw_status(screen, &editor); From 9514985802308e2a0366bbdddfcd7ffc8c5bd41c Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:22:49 +0000 Subject: [PATCH 13/16] fixes for welcome message --- src/tui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui.rs b/src/tui.rs index 60ca480..148acc2 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -46,7 +46,7 @@ pub fn draw_status(screen: &mut Screen, editor: &Editor) { pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { // The welcome message let message: [&str; 6] = [ - &"λ Lambda".bright_yellow() as &str, + &"λ Lambda".yellow() as &str, "Hackable text editor for nerds", "", "Type :help to open the README.md document", From a06e9bfe26d6448a32eaca73e3c13e7c6ad06947 Mon Sep 17 00:00:00 2001 From: Maddie <32415621+SpyHoodle@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:33:49 +0000 Subject: [PATCH 14/16] updated readme and todo --- README.md | 9 ++++++++- TODO.md | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f2d1b6..7389f60 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,14 @@ The main goal is to build the best text editor possible by taking ideas from exi - Lambda is written in Rust, so it's incredibly fast and logical - It's also full of comments, so anyone can try and learn what it's doing -- Lambda is very modular, so features can be easily added +- Lambda is very modular and extensible, so features can be easily added through a variety of methods + - Need to run a set of keybinds in order? Create a macro + - Need to create a completely new feature? Just fork it and write it in Rust +- Lambda is separated between the core and the standard terminal implementation + - This means that anyone can implement their own keybinds, ui, themes, styling, etc. + - This also means that there is one standard way for managing the text itself - inside of the lambda core +- Lambda is a modal text editor and uses ideas from kakoune, and is designed for the select -> action structure + - Since anyone can implement their own keybinds, it's possible to make a vim implementation that uses the action -> select structure - 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 diff --git a/TODO.md b/TODO.md index b3646b2..072c505 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +1,4 @@ - [ ] Remove colored in favour of crossterm's styling +- [ ] Use &str for writing text +- [ ] Make sure borrows for Screen and Editor are correct +- [ ] Fix small terminal screen issues From 6f2436c711d81e311fa9e7eb7d1826f805e9cc61 Mon Sep 17 00:00:00 2001 From: Madeleine Date: Mon, 7 Nov 2022 18:50:10 +0000 Subject: [PATCH 15/16] fixes & better structure (done todos) --- Cargo.lock | 38 --------------- Cargo.toml | 1 - TODO.md | 8 ++-- src/editor.rs | 7 ++- src/terminal.rs | 25 +++++++--- src/tui.rs | 122 +++++++++++++++++++++++++++++++----------------- 6 files changed, 107 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 943f46e..aa77f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -31,17 +20,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - [[package]] name = "crossterm" version = "0.25.0" @@ -67,29 +45,13 @@ dependencies = [ "winapi", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "lambda" version = "0.1.0" dependencies = [ - "colored", "crossterm", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.137" diff --git a/Cargo.toml b/Cargo.toml index 43281cd..23443e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,3 @@ edition = "2021" [dependencies] crossterm = "0.25" -colored = "2" diff --git a/TODO.md b/TODO.md index 072c505..f680904 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -- [ ] Remove colored in favour of crossterm's styling -- [ ] Use &str for writing text -- [ ] Make sure borrows for Screen and Editor are correct -- [ ] Fix small terminal screen issues +- [X] Remove colored in favour of crossterm's styling +- [X] Use &str for writing text -> can't really be done +- [X] Make sure borrows for Screen and Editor are correct +- [X] Fix small terminal screen issues diff --git a/src/editor.rs b/src/editor.rs index bc2f5d7..ecb4a2c 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,10 +1,14 @@ pub struct Config<'a> { pub logo: &'a str, + pub friendly_name: &'a str, } impl<'a> Config<'a> { pub fn new() -> Self { - Self { logo: "λ" } + Self { + logo: "λ", + friendly_name: "Lambda", + } } } @@ -14,6 +18,7 @@ pub struct Buffer<'a> { pub path: &'a str, } +#[allow(dead_code)] pub enum Mode { Normal, Insert, diff --git a/src/terminal.rs b/src/terminal.rs index d1f7dc3..2d32374 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -105,6 +105,19 @@ impl Screen { // Terminal functions and methods for managing the terminal impl Screen { + pub fn refresh(&mut self) -> Result<(), ErrorKind>{ + // Clear the screen + Screen::clear(); + + // Update the screen dimensions + let size = terminal::size()?; + self.size.width = size.0 as usize; + self.size.height = size.1 as usize; + + // Return Ok if was successful + Ok(()) + } + pub fn enter(&mut self) { // Hide the cursor self.cursor.hide(); @@ -123,19 +136,19 @@ impl Screen { terminal::disable_raw_mode().unwrap(); } - pub fn write(text: String) { - // Writes a line to a current cursor position - execute!(stdout(), Print(text)).unwrap(); - } - pub fn clear() { // Clears the terminal screen execute!(stdout(), terminal::Clear(terminal::ClearType::All)).unwrap(); } + pub fn write(text: &str) { + // Writes a line to a current cursor position + execute!(stdout(), Print(text)).unwrap(); + } + pub fn write_at(&mut self, text: String, position: Coords) { // Writes a line at a set of coordinates self.cursor.move_to(position); - Screen::write(text); + Screen::write(&text); } } diff --git a/src/tui.rs b/src/tui.rs index 148acc2..c6a5343 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,52 +1,74 @@ use crate::editor::Editor; use crate::terminal::{Coords, Screen}; -use colored::Colorize; +use crossterm::style::Stylize; use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; -pub fn draw_status(screen: &mut Screen, editor: &Editor) { +// Utils +fn with_spaces(text: &str) -> String { + format!(" {} ", text) +} +fn calc_x(screen_width: usize, item_length: usize) -> usize { + (screen_width / 2) - (item_length / 2) +} +fn longest_element_in_array(elements: Vec<&str>) -> usize { + elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len() +} + +pub fn draw_status(screen: &mut Screen, editor: &Editor) -> Result<(), ()> { // Calculate where to draw the status bar let status_height = screen.size.height - 2; // Get the editor logo from the config - let editor_logo = &format!(" {} ", editor.config.logo) as &str; - // Write the editor logo - screen.write_at( - editor_logo.bright_yellow().bold().reversed().to_string(), - Coords::from(0, status_height), - ); - + let editor_logo = &with_spaces(editor.config.logo) as &str; // Get the current mode into a string - let mode_string = &format!(" {} ", editor.mode.as_str()) as &str; - // Calculate where to write the current mode - let x = editor_logo.len() - 1; - // Write the current mode - screen.write_at( - mode_string.green().bold().reversed().to_string(), - Coords::from(x, status_height), - ); - + let mode_string = &with_spaces(editor.mode.as_str()) as &str; // Get the current open file name - let file_name = &format!(" {} ", editor.buffer.name) as &str; - // Calculate where to write the file name - let x = x + mode_string.len(); - // Write the current file name - screen.write_at( - file_name.magenta().bold().reversed().to_string(), - Coords::from(x, status_height), - ); + let file_name = &with_spaces(editor.buffer.name) as &str; - // Draw the rest of the status bar - let x = x + file_name.len(); - screen.write_at( - " ".repeat(screen.size.width - x).reversed().to_string(), - Coords::from(x, status_height), - ); + // Calculate the total length of all the status bar components + let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1; + + // If the screen isn't wide enough, panic as we can't draw the status bar + if screen.size.width < total_length { + Err(()) + + } else { + // Write the editor logo + screen.write_at( + editor_logo.yellow().bold().reverse().to_string(), + Coords::from(0, status_height), + ); + + // Calculate where to write the current mode + let x = editor_logo.len() - 1; + // Write the current mode + screen.write_at( + mode_string.green().bold().reverse().to_string(), + Coords::from(x, status_height), + ); + // Calculate where to write the file name + let x = x + mode_string.len(); + // Write the current file name + screen.write_at( + file_name.magenta().bold().reverse().to_string(), + Coords::from(x, status_height), + ); + + // Draw the rest of the status bar + let x = x + file_name.len(); + screen.write_at( + " ".repeat(screen.size.width - x).reverse().to_string(), + Coords::from(x, status_height), + ); + + Ok(()) + } } pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { // The welcome message - let message: [&str; 6] = [ - &"λ Lambda".yellow() as &str, + let title = format!("{} {}", editor.config.logo, editor.config.friendly_name); + let message: [&str; 5] = [ "Hackable text editor for nerds", "", "Type :help to open the README.md document", @@ -54,28 +76,40 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { "Type :q! or to quit lambda", ]; - // The starting y position in the centre of the screen - let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; + // If the screen is big enough, we can draw + if screen.size.width > longest_element_in_array(message.to_vec()) && screen.size.height > message.len() + 4 { + // The starting y position in the centre of the screen + let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; - for line in message { - // Each line has different width so requires a different x position to center it - let x = (screen.size.width / 2) - (line.len() / 2); + // Calculate where to place the title + let x = calc_x(screen.size.width, title.len()); - // For each line we move downwards so increment y - y += 1; + // Write the title to the screen + screen.write_at(title.yellow().to_string(), Coords::from(x, y)); - // Write the line to the screen at position (x, y) - screen.write_at(line.to_string(), Coords::from(x, y)); + for line in message { + // Each line has different width so requires a different x position to center it + let x = calc_x(screen.size.width, line.len()) ; + + // For each line we move downwards so increment y + y += 1; + + // Write the line to the screen at position (x, y) + screen.write_at(line.to_string(), Coords::from(x, y)); + } } } pub fn start(screen: &mut Screen, editor: Editor) { loop { + // Refresh the screen + screen.refresh().unwrap(); + // Draw the welcome message draw_welcome(screen, &editor); // Draw the status bar - draw_status(screen, &editor); + draw_status(screen, &editor).unwrap(); // Check for any key presses match read().unwrap() { From b6a4c04a250e861dfe26e1850b0ad379698c8fdf Mon Sep 17 00:00:00 2001 From: Madeleine Date: Mon, 7 Nov 2022 20:08:28 +0000 Subject: [PATCH 16/16] cargo.toml + todo.md updated --- Cargo.toml | 11 +++++++++++ TODO.md | 6 ++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23443e5..894e8dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,18 @@ [package] name = "lambda" version = "0.1.0" +authors = ["Madeline "] edition = "2021" +description = "Next generation hackable text editor for nerds" +homepage = "https://github.com/SpyHoodle/lambda" +repository = "https://github.com/SpyHoodle/lambda" +readme = "README.md" +include = ["src/*.rs", "Cargo.toml"] +categories = ["text-editors"] +keywords = ["text-editor", "editor", "terminal", "tui"] + +[profile.release] +panic = abort [dependencies] crossterm = "0.25" diff --git a/TODO.md b/TODO.md index f680904..798b9f4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,2 @@ -- [X] Remove colored in favour of crossterm's styling -- [X] Use &str for writing text -> can't really be done -- [X] Make sure borrows for Screen and Editor are correct -- [X] Fix small terminal screen issues +- [ ] Make components (i.e statusbar) modular +- [ ] Modularise functions