✨ Clean up, modularisation & refactoring
This commit is contained in:
parent
f312672152
commit
9126abc6d5
17
README.md
17
README.md
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
40
main.py
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user