Clean up, modularisation & refactoring

This commit is contained in:
Maddie H 2022-04-20 11:47:12 +01:00
parent f312672152
commit 9126abc6d5
9 changed files with 101 additions and 65 deletions

View File

@ -1,27 +1,32 @@
# λ lambda # λ lambda
Next generation hackable text editor for nerds. Next generation hackable text editor for nerds.
### Let it be known! ### Let it be known!
Lambda is in *very* early stages at the moment. Features may change completely, or even be removed.<br> Lambda is in *very* early stages at the moment. Features may change completely, or even be removed.<br>
Don't expect lambda to stay the way it is. Updates are pushed often. Don't expect lambda to stay the way it is. Updates are pushed often.
### Overview ### Overview
Lambda is a similar text editor to `vim` or `kakoune`.<br> Lambda is a similar text editor to `vim` or `kakoune`.<br>
However, it takes a different approach to most of the features seen in other editors. 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. - 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 is incredibly modular, so you can easily add new features.
- Lambda follows the unix philosophy of "do one thing and do it well." - Lambda follows the unix philosophy of "do one thing and do it well."
- It has no bloated features, like splits or tabs - It has no bloated features, like splits or tabs
- It contains the bare necessities and provides a few extra modules - It contains the bare necessities and provides a few extra modules
- Lambda isn't limited to modes or keybindings. - Lambda isn't limited to modes or keybindings.
- Keybindings and modes can be easily changed - Keybindings and modes can be easily changed
- Other modes can be used by holding down keybindings (i.e. `ctrl-x` inside of `insert` mode) - 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. - 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. - Lambda has much better default keybindings than other text editors.
### Getting started ### Getting started
```bash ```bash
git clone https://github.com/SpyHoodle/lambda.git # Clone the repository git clone https://github.com/SpyHoodle/lambda.git # Clone the repository
cd lambda # Enter lambda directory cd lambda # Enter lambda directory

View File

@ -21,17 +21,38 @@ class Buffer:
instance.components.components["left"]) + len(line), " " * (instance.safe_width - len(line))) instance.components.components["left"]) + len(line), " " * (instance.safe_width - len(line)))
@staticmethod @staticmethod
def remove_char(instance): def delete_line(instance, y: int = None):
# Remove a character from a string at a given index # Default to the cursor position
instance.buffer.data[instance.cursor[0]] = instance.buffer.data[instance.cursor[0]][:(instance.cursor[1] - 1)] \ y = y or instance.cursor[0]
+ instance.buffer.data[instance.cursor[0]][(instance.cursor[1] - 1) + 1:]
# Remove a line from the buffer
instance.buffer.data.pop(y)
@staticmethod @staticmethod
def insert_char(instance, char: (str, chr)): def insert_line(instance, y: int = None):
# Insert a character into a string at a given index # Default to the cursor position
instance.buffer.data[instance.cursor[0]] = instance.buffer.data[instance.cursor[0]][:instance.cursor[1]] + \ y = y or instance.cursor[0]
char + \
instance.buffer.data[instance.cursor[0]][instance.cursor[1]:] # 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): def open_file(file_path):

View File

@ -22,30 +22,30 @@ def init_colors():
curses.start_color() curses.start_color()
# Foreground: WHITE, Background: BLACK # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # Foreground: MAGENTA, Background: BLACK
curses.init_pair(13, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(13, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
# Foreground: BLACK, Background: MAGENTA # Foreground: BLACK, Background: MAGENTA
curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA) curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA)

View File

@ -18,7 +18,7 @@ class StatusBar:
def render(self, instance): def render(self, instance):
# Clear the status bar # Clear the status bar
utils.clear(instance, instance.height - 2, 0) utils.clear_line(instance, instance.height - 2, 0)
# Update variables # Update variables
self.update(instance) self.update(instance)

View File

@ -17,12 +17,12 @@ def gracefully_exit():
sys.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 # Clear the line at the screen at position y, x
instance.screen.insstr(y, x, " " * (instance.width - x)) instance.screen.insstr(y, x, " " * (instance.width - x))
def pause(message: str): def pause_screen(message: str):
# End the curses session # End the curses session
curses.endwin() curses.endwin()
@ -40,17 +40,22 @@ def save_file(instance, file_path: str, data: list):
# Save the data to the file # Save the data to the file
with open(file_path, "w") as f: with open(file_path, "w") as f:
try: try:
# For each line in the file
for index, line in enumerate(data): for index, line in enumerate(data):
if index == len(data) - 1: if index == len(data) - 1:
# If this is the last line, write it without a newline
f.write(line) f.write(line)
else: else:
# Otherwise, write the line with a newline
f.write(f"{line}\n") 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.") 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 # Parse the path of the config file
config_file_path = f"{Path.home()}/.config/lambda/config.json" config_file_path = f"{Path.home()}/.config/lambda/config.json"
@ -60,10 +65,7 @@ def load_config() -> dict:
return load_file(config_file_path) return load_file(config_file_path)
def welcome(screen): def welcome(instance):
# Get window height and width
height, width = screen.getmaxyx()
# Startup text # Startup text
title = "λ Lambda" title = "λ Lambda"
subtext = [ subtext = [
@ -75,17 +77,17 @@ def welcome(screen):
] ]
# Centering calculations # Centering calculations
start_x_title = int((width // 2) - (len(title) // 2) - len(title) % 2) start_x_title = int((instance.safe_width // 2) - (len(title) // 2) - len(title) % 2 + 2)
start_y = int((height // 2) - 2) start_y = int((instance.safe_height // 2) - 1)
# Rendering title # 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 # Print the subtext
for text in subtext: for text in subtext:
start_y += 1 start_y += 1
start_x = int((width // 2) - (len(text) // 2) - len(text) % 2) start_x = int((instance.safe_width // 2) - (len(text) // 2) - len(text) % 2 + 2)
screen.addstr(start_y, start_x, text) instance.screen.addstr(start_y, start_x, text)
def prompt(instance, message: str, color: int = 1) -> (list, None): 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 = [] inp = []
# Write whitespace over characters to refresh it # 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 # Write the message to the screen
instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color)) 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) # Subtracting a key (backspace)
if key in (curses.KEY_BACKSPACE, 127, '\b'): if key in (curses.KEY_BACKSPACE, 127, '\b'):
# Write whitespace over characters to refresh it # 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: if inp:
# Subtract a character from the input list # 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") cursors.mode("hidden")
# Clear the bottom of the screen # 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 # 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, 0, message, curses.color_pair(color))
@ -158,7 +160,7 @@ def press_key_to_continue(instance, message: str, color: int = 1):
instance.screen.getch() instance.screen.getch()
# Clear the bottom of the screen # Clear the bottom of the screen
clear(instance, instance.height - 1, 0) clear_line(instance, instance.height - 1, 0)
# Show the cursor # Show the cursor
cursors.mode("visible") cursors.mode("visible")
@ -196,7 +198,7 @@ def goodbye(instance):
# Clear the prompt if the user cancels # Clear the prompt if the user cancels
else: else:
clear(instance, instance.height - 1, 0) clear_line(instance, instance.height - 1, 0)
except KeyboardInterrupt: except KeyboardInterrupt:
# If the user presses Ctrl+C, just exit # If the user presses Ctrl+C, just exit

View File

@ -6,6 +6,6 @@ cp -rf . ~/.local/share/lambda
# Copy lambda launcher # Copy lambda launcher
chmod +x ./lambda chmod +x ./lambda
rm -r ~/.local/bin/lambda rm -rf ~/.local/bin/lambda
ln -s ~/.local/share/lambda/lambda ~/.local/bin/lambda ln -s ~/.local/share/lambda/lambda ~/.local/bin/lambda
chmod +x ~/.local/bin/lambda chmod +x ~/.local/bin/lambda

40
main.py
View File

@ -62,25 +62,37 @@ class Lambda:
# Show a welcome message if lambda opens with no file # Show a welcome message if lambda opens with no file
if not self.buffer.path: if not self.buffer.path:
utils.welcome(self.screen) # Update the screen variables
self.refresh()
# Show the welcome message
utils.welcome(self)
# Main loop # Main loop
self.run() self.run()
def run(self): def run(self):
# The main loop, which runs until the user quits try:
while True: # The main loop, which runs until the user quits
# Update the screen variables while True:
self.refresh() # Update the screen variables
self.refresh()
# Wait for a keypress # Wait for a keypress
key = self.screen.getch() key = self.screen.getch()
# Handle the key # Handle the key
modes.handle_key(self, key) modes.handle_key(self, key)
# Refresh and clear the screen # Refresh and clear the screen
self.screen.erase() self.screen.erase()
except KeyboardInterrupt: # Ctrl-C
# Create a goodbye prompt
utils.goodbye(self)
# Run the main loop again
self.run()
def main(): def main():
@ -96,7 +108,7 @@ def main():
buffer = buffers.load_file(args.file) buffer = buffers.load_file(args.file)
# Load the config # Load the config
config = utils.load_config() config = utils.load_config_file()
# Load lambda with the buffer object # Load lambda with the buffer object
instance = Lambda(buffer, config) instance = Lambda(buffer, config)
@ -105,10 +117,6 @@ def main():
try: try:
instance.start() instance.start()
# KeyboardInterrupt is thrown when <C-c> is pressed (exit)
except KeyboardInterrupt:
utils.goodbye(instance)
# Excepts *any* errors that occur # Excepts *any* errors that occur
except Exception as exception: except Exception as exception:
utils.fatal_error(exception) utils.fatal_error(exception)

View File

@ -11,7 +11,7 @@ def execute(instance, key):
elif key in (curses.KEY_BACKSPACE, 127, '\b'): # Backspace elif key in (curses.KEY_BACKSPACE, 127, '\b'): # Backspace
if instance.cursor[1] > 0: if instance.cursor[1] > 0:
# Delete the character before the cursor # Delete the character before the cursor
instance.buffer.remove_char(instance) instance.buffer.delete_char(instance)
# Move the cursor one to the left # Move the cursor one to the left
cursors.push(instance, 3) cursors.push(instance, 3)