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