diff --git a/.gitignore b/.gitignore index 3a8cabc..9efad1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -/target -.idea +__pycache__ +core/__pycache__ +mode/__pycache__ +.idea \ No newline at end of file diff --git a/README.md b/README.md index d69472b..3d472e2 100644 --- a/README.md +++ b/README.md @@ -1 +1,14 @@ -# lambda +# λ lambda +Next generation hackable text editor for nerds. + +### Overview +Lambda is a similar text editor to `vim` or `kakoune`.
+However, it has a few key differences: +- Lambda is written in Python, so it is easy to hack and learn. +- Lambda is incredibly modular, so you can easily add new features. +- Lambda is written in a way that makes it easy to use in a terminal. +- 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 is extremely fast and makes use of efficient memory management. +- Lambda has much better default keybindings than other text editors. diff --git a/core/components.py b/core/components.py index db98030..e640b02 100644 --- a/core/components.py +++ b/core/components.py @@ -4,19 +4,18 @@ import curses class StatusBar: def __init__(self, instance): - self.instance = instance self.mode = instance.mode.upper() self.file = instance.buffer.name or "[No Name]" self.icon = instance.config["icon"] or "λ" self.colors = (7, 5, 13) self.components = [self.icon, self.mode, self.file] - def render(self): + def render(self, instance): x = 1 - utils.clear(self.instance, self.instance.height - 2, 0) + utils.clear(instance, instance.height - 2, 0) for count, component in enumerate(self.components): - self.instance.screen.addstr(self.instance.height - 2, x, component, - curses.color_pair(self.colors[count]) | curses.A_BOLD) + instance.screen.addstr(instance.height - 2, x, component, + curses.color_pair(self.colors[count]) | curses.A_BOLD) x += len(component) + 1 @@ -28,4 +27,4 @@ class Components: def render(self, instance): for component in self.components["bottom"]: - component(instance).render() + component(instance).render(instance) diff --git a/core/modes.py b/core/modes.py index 64ecd7c..9a3abed 100644 --- a/core/modes.py +++ b/core/modes.py @@ -1,33 +1,29 @@ from mode import normal, insert, command -def activate(instance, mode) -> object: +def activate(instance, mode): # Visibly update the mode instance.mode = mode instance.update() if mode == "command": - instance = command.activate(instance) + # Activate command mode + command.activate(instance) elif mode == "insert": - instance = insert.activate(instance) + # Activate insert mode + insert.activate(instance) elif mode == "normal": - instance = normal.activate(instance) - - return instance + # Activate normal mode + normal.activate(instance) def handle_key(instance, key): + # Normal mode - default keybindings if instance.mode == "normal": - instance = normal.execute(instance, key) + normal.execute(instance, key) # Insert mode - inserting text to the buffer elif instance.mode == "insert": - instance = insert.execute(instance, key) - - # Command mode - extra commands for lambda - elif instance.mode == "command": - instance = command.activate(instance) - - return instance + insert.execute(instance, key) diff --git a/lambda b/lambda index 8494e3f..54580fd 100755 --- a/lambda +++ b/lambda @@ -1,3 +1,3 @@ #!/bin/sh -python3 ./lambda.py "$@" +python3 ./main.py "$@" diff --git a/main.py b/main.py index 964e2f2..de0f2a8 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,10 @@ class Lambda: self.config = {"icon": "λ"} 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"]) def update(self): @@ -41,7 +44,7 @@ class Lambda: # Change the cursor shape cursors.cursor_mode("block") - # Turn no echo on + # Don't echo any key-presses curses.noecho() # Show a welcome message if lambda opens with no file @@ -52,6 +55,7 @@ class Lambda: self.run() def run(self): + # The main loop, which runs until the user quits while True: # Write the buffer to the screen # buffers.write_buffer(screen, buffer) @@ -71,38 +75,47 @@ class Lambda: def main(): - # Command line arguments + # 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 + # Collect the arguments passed into lambda at the shell args = parser.parse_args() - # Load the file into buffer + # Load the file into a Buffer object buffer = buffers.load_file(args.file) # Change the escape delay to 25ms # Fixes an issue where esc takes way too long to press os.environ.setdefault("ESCDELAY", "25") - # Load lambda with the buffer + # Load lambda with the buffer object screen = Lambda(buffer) - # Start the screen + # Start the screen, this will loop until exit try: screen.start() - # KeyboardInterrupt is thrown when ctrl+c is pressed + # KeyboardInterrupt is thrown when is pressed (exit) except KeyboardInterrupt: + # Clean up the screen curses.endwin() + + # Then, just exit sys.exit() + # Excepts *any* errors that occur except Exception as exception: + # Clean up the screen curses.endwin() + + # Print the error message and traceback print(f"{colors.Codes.red}FATAL ERROR:{colors.Codes.end} " f"{colors.Codes.yellow}{exception}{colors.Codes.end}\n") print(traceback.format_exc()) + + # Exit, with an error code sys.exit(0) diff --git a/mode/command.py b/mode/command.py index 82a4a5a..901ce78 100644 --- a/mode/command.py +++ b/mode/command.py @@ -2,33 +2,31 @@ from core import utils def execute(instance, commands): - if not commands: - # Quit if there are no commands, don't check anything - return instance + # Only if commands are given + if commands: + # Check each command in the list of commands + for command in commands: + # Write + if command == "w": + # Write to the file + pass - for command in commands: - if command == "w": - # Write to the file - pass + # Quit + elif command == "q": + # Load a goodbye prompt + utils.goodbye(instance) - elif command == "q": - # Load a goodbye prompt - utils.goodbye(instance) - - else: - utils.error(instance, f"not an editor command: '{command}'") - - return instance + # Unknown command + else: + utils.error(instance, f"not an editor command: '{command}'") def activate(instance): - # Start the prompt + # Create a prompt, which returns the input (commands) commands = utils.prompt(instance, ":") - # Execute the commands - instance = execute(instance, commands) + # Execute the commands given + execute(instance, commands) - # Return to normal mode + # Return to normal mode once all commands are executed instance.mode = "normal" - - return instance diff --git a/mode/insert.py b/mode/insert.py index ba0ea9b..77ef3de 100644 --- a/mode/insert.py +++ b/mode/insert.py @@ -1,18 +1,13 @@ -from core import cursors -from mode import normal +from core import cursors, modes def execute(instance, key): + # Enter key if key == 27: # Switch to normal mode - instance.mode = "normal" - normal.activate(instance) - - return instance + modes.activate(instance, "normal") def activate(instance): # Switch the cursor to a line cursors.cursor_mode("line") - - return instance diff --git a/mode/normal.py b/mode/normal.py index cd3d9de..bf3919a 100644 --- a/mode/normal.py +++ b/mode/normal.py @@ -22,24 +22,20 @@ def execute(instance, key): elif key == ord("i"): # Activate insert mode - instance = modes.activate(instance, "insert") + modes.activate(instance, "insert") elif key == ord("I"): # Move the cursor to the right instance.cursor = cursors.cursor_push(instance.cursor, "right") # Then activate insert mode - instance = modes.activate(instance, "insert") + modes.activate(instance, "insert") elif key in (ord(":"), ord(";")): # Activate command mode - instance = modes.activate(instance, "command") - - return instance + modes.activate(instance, "command") -def activate(instance): +def activate(): # Switch the cursor to a block cursors.cursor_mode("block") - - return instance \ No newline at end of file