Compare commits

..

10 Commits

Author SHA1 Message Date
7ff58d6f76
renamed to velocity 2022-12-05 14:11:48 +00:00
Maddie
6c12ccb4c1 something better with buffers 2022-12-05 14:05:22 +00:00
Maddie
08081d54e2 added buffer types and takes in buffer name better 2022-11-18 14:20:49 +00:00
cb005ef441
finished loading buffer names 2022-11-12 19:57:58 +00:00
Maddie
5b49d25d9e building up buffer 2022-11-11 17:25:29 +00:00
Maddie
6f792ff546 separate terminal from ui 2022-11-11 11:57:40 +00:00
Maddie
25b0da3962 some modularisation and cleanup 2022-11-11 11:34:52 +00:00
Maddie
1d18cd1715 reverted oop changes 2022-11-11 11:15:33 +00:00
a8c7733c0a
added a comment 2022-11-10 07:28:54 +00:00
e050cb568c
ui components working 2022-11-10 06:44:18 +00:00
19 changed files with 201 additions and 290 deletions

20
.replit Normal file
View File

@ -0,0 +1,20 @@
run = "cargo run"
hidden = ["target"]
[packager]
language = "rust"
[packager.features]
packageSearch = true
[languages.rust]
pattern = "**/*.rs"
[languages.rust.languageServer]
start = "rust-analyzer"
[nix]
channel = "stable-22_05"
[gitHubImport]
requiredFiles = [".replit", "replit.nix"]

14
Cargo.lock generated
View File

@ -45,13 +45,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "lambda"
version = "0.1.0"
dependencies = [
"crossterm",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.137"
@ -163,6 +156,13 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "velocity"
version = "0.1.0"
dependencies = [
"crossterm",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View File

@ -1,11 +1,11 @@
[package] [package]
name = "lambda" name = "velocity"
version = "0.1.0" version = "0.1.0"
authors = ["Madeline <maddie@spyhoodle.me>"] authors = ["Madeline <maddie@spyhoodle.me>"]
edition = "2021" edition = "2021"
description = "Next generation hackable text editor for nerds" description = "Next generation hackable text editor for nerds"
homepage = "https://github.com/SpyHoodle/lambda" homepage = "https://github.com/SpyHoodle/velocity"
repository = "https://github.com/SpyHoodle/lambda" repository = "https://github.com/SpyHoodle/velocity"
readme = "README.md" readme = "README.md"
include = ["src/*.rs", "Cargo.toml"] include = ["src/*.rs", "Cargo.toml"]
categories = ["text-editors"] categories = ["text-editors"]

View File

@ -1,29 +1,29 @@
# λ Lambda # λ Velocity
A next-generation hackable incredibly performant rust text editor for nerds. 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. > ⚠️ Velocity is in *very* early stages at the moment. Velocity's goals are still being decided and features may completely change.
## Overview ## Overview
Lambda is a similar text editor to `vim` or `kakoune`, taking some ideas from `xi`. Velocity 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. 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 - Velocity 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 - It's also full of comments, so anyone can try and learn what it's doing
- Lambda is very modular and extensible, so features can be easily added through a variety of methods - Velocity 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 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 - 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 - Velocity is separated between the core and the standard terminal implementation
- This means that anyone can implement their own keybinds, ui, themes, styling, etc. - 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 - This also means that there is one standard way for managing the text itself - inside of the Velocity core
- Lambda is a modal text editor and uses ideas from kakoune, and is designed for the select -> action structure - Velocity 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 - 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" - Velocity 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 has much better default keybindings than other text editors - Velocity has much better default keybindings than other text editors
## Getting started ## Getting started
You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`. You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`.
```bash ```bash
git clone https://github.com/SpyHoodle/lambda.git # Clone the repositiory git clone https://github.com/SpyHoodle/velocity.git # Clone the repositiory
cargo run # Build and run lambda! cargo run # Build and run velocity!
``` ```

View File

@ -1,2 +0,0 @@
- [ ] Modularise functions
- [ ] Make components (i.e statusbar) modular

9
replit.nix Normal file
View File

@ -0,0 +1,9 @@
{ pkgs }: {
deps = [
pkgs.rustc
pkgs.rustfmt
pkgs.cargo
pkgs.cargo-edit
pkgs.rust-analyzer
];
}

37
src/core/buffer.rs Normal file
View File

@ -0,0 +1,37 @@
use std::path::PathBuf;
#[derive(PartialEq)]
pub enum BufferKind {
Scratch,
Write,
Read,
}
impl BufferKind {
pub fn as_str(&self) -> &str {
match self {
BufferKind::Scratch => "*scratch*",
BufferKind::Write => "write",
BufferKind::Read => "read",
}
}
}
pub struct Buffer<'a> {
pub data: Vec<String>,
pub path: PathBuf,
pub kind: BufferKind,
pub name: &'a str,
}
impl<'a> Buffer<'a> {
pub fn new(path: PathBuf, name: &'a str, kind: BufferKind) -> Self {
// Return a buffer
Self {
data: vec![String::from("")],
path,
kind,
name,
}
}
}

View File

@ -1,3 +1,7 @@
use std::path::PathBuf;
use crate::core::buffer::Buffer;
use crate::core::buffer::BufferKind;
pub struct Config<'a> { pub struct Config<'a> {
pub logo: &'a str, pub logo: &'a str,
pub friendly_name: &'a str, pub friendly_name: &'a str,
@ -7,17 +11,11 @@ impl<'a> Config<'a> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
logo: "λ", logo: "λ",
friendly_name: "Lambda", friendly_name: "Velocity",
} }
} }
} }
pub struct Buffer<'a> {
pub data: Vec<String>,
pub name: &'a str,
pub path: &'a str,
}
#[allow(dead_code)] #[allow(dead_code)]
pub enum Mode { pub enum Mode {
Normal, Normal,
@ -39,20 +37,17 @@ impl Mode {
pub struct Editor<'a> { pub struct Editor<'a> {
pub config: Config<'a>, pub config: Config<'a>,
pub buffer: Buffer<'a>, pub buffer: Box<Buffer<'a>>,
pub cursors: Vec<i32>, pub cursors: Vec<i32>,
pub mode: Mode, pub mode: Mode,
} }
impl<'a> Editor<'a> { impl<'a> Editor<'a> {
pub fn new() -> Self { pub fn new(path: PathBuf, buffer_name: &'a str) -> Self {
let buffer_kind = if path.to_str().unwrap().len() > 1 { BufferKind::Write } else { BufferKind::Scratch };
Editor { Editor {
config: Config::new(), config: Config::new(),
buffer: Buffer { buffer: Box::new(Buffer::new(path, buffer_name, buffer_kind)),
data: Vec::from([String::from("Hello"), String::from("World")]),
name: "[No Name]",
path: "/home/spy",
},
cursors: Vec::from([0]), cursors: Vec::from([0]),
mode: Mode::Normal, mode: Mode::Normal,
} }

View File

@ -1 +1,2 @@
pub mod editor; pub mod editor;
pub mod buffer;

View File

@ -1,9 +1,25 @@
mod core; mod core;
mod terminal; mod terminal;
mod tui; mod tui;
use std::{env, path::PathBuf};
fn main() { fn main() {
let lambda = core::editor::Editor::new(); // Collect command line arguments
let args: Vec<String> = env::args().collect();
// Collect the file path
let file_path = if args.len() > 1 { PathBuf::from(&args[1]) } else { PathBuf::from("") };
// Collect the file name
let file_name = file_path.clone();
let file_name = if args.len() > 1 { file_name.file_name().unwrap().to_str().unwrap() } else { "" };
// Initalise a new editor
let velocity = core::editor::Editor::new(file_path, file_name);
// Initalise a screen
let mut screen = terminal::screen::Screen::new().unwrap(); let mut screen = terminal::screen::Screen::new().unwrap();
tui::ui::Ui::run(&mut screen, lambda);
// Begin lambda
tui::ui::start(&mut screen, velocity);
} }

View File

@ -1 +1 @@
pub mod screen; pub mod screen;

View File

@ -11,7 +11,7 @@ pub struct Coords {
pub y: usize, pub y: usize,
} }
// Creating a set of coordinates from two values // Creating a coordinates from two values
impl Coords { impl Coords {
pub fn from(x: usize, y: usize) -> Self { pub fn from(x: usize, y: usize) -> Self {
Self { x, y } Self { x, y }

View File

@ -1,131 +0,0 @@
use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen};
use crossterm::style::Stylize;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
// 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 = &with_spaces(editor.config.logo) as &str;
// Get the current mode into a string
let mode_string = &with_spaces(editor.mode.as_str()) as &str;
// Get the current open file name
let file_name = &with_spaces(editor.buffer.name) as &str;
// 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 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",
"Type :o <file> to open a file and edit",
"Type :q! or <C-c> to quit lambda",
];
// 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;
// Calculate where to place the title
let x = calc_x(screen.size.width, title.len());
// Write the title to the screen
screen.write_at(title.yellow().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).unwrap();
// Check for any key presses
match read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
_ => (),
}
}
screen.exit();
}

View File

@ -1,2 +1,2 @@
pub mod statusbar; pub mod statusbar;
pub mod welcome; pub mod welcome;

View File

@ -1,74 +1,63 @@
use crate::tui::ui::Component; use crossterm::style::Stylize;
use crate::tui::utils::with_spaces;
use crate::core::editor::Editor; use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen}; use crate::terminal::screen::{Coords, Screen};
use crossterm::style::Stylize; use crate::tui::utils;
pub struct StatusBar<'a> { pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
logo: &'a str, // Calculate where to draw the status bar
mode: &'a str, let status_height = screen.size.height - 2;
file_name: &'a str,
}
impl<'a> StatusBar<'a> { // Get the editor logo from the config
pub fn new(editor: &'a Editor<'a>) -> Self { let editor_logo = &utils::with_spaces(editor.config.logo) as &str;
Self { // Get the current mode into a string
logo: editor.config.logo, let mode_string = &utils::with_spaces(editor.mode.as_str()) as &str;
mode: editor.mode.as_str(), // Get the current open file name
file_name: editor.buffer.name, let file_name = &utils::with_spaces(editor.buffer.name) as &str;
} // Get the current buffer kind
} let buffer_kind = &utils::with_spaces(editor.buffer.kind.as_str()) as &str;
}
impl<'a> Component for StatusBar<'a> { // Calculate the total length of all the status bar components
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()> { let total_length = editor_logo.len() + mode_string.len() + file_name.len() + buffer_kind.len() + 1;
// Calculate where to draw the status bar
let status_height = screen.size.height - 2;
// Get the editor logo from the config // If the screen isn't wide enough, panic as we can't draw the status bar
let editor_logo = &with_spaces(editor.config.logo) as &str; if screen.size.width < total_length {
// Get the current mode into a string Err(())
let mode_string = &with_spaces(editor.mode.as_str()) as &str;
// Get the current open file name
let file_name = &with_spaces(editor.buffer.name) as &str;
// Calculate the total length of all the status bar components } else {
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1; // Write the editor logo
screen.write_at(
editor_logo.yellow().bold().reverse().to_string(),
Coords::from(0, status_height),
);
// If the screen isn't wide enough, panic as we can't draw the status bar // Calculate where to write the current mode
if screen.size.width < total_length { let x = editor_logo.len() - 1;
Err(()) // Write the current mode
screen.write_at(
mode_string.green().bold().reverse().to_string(),
Coords::from(x, status_height),
);
} else { let x = x + mode_string.len();
// Write the editor logo // Draws the file name if it has a length, if not then it will draw the buffer type
screen.write_at( if editor.buffer.name.len() > 0 {
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( screen.write_at(
file_name.magenta().bold().reverse().to_string(), file_name.magenta().bold().reverse().to_string(),
Coords::from(x, status_height), Coords::from(x, status_height),
); );
} else {
// Draw the rest of the status bar
let x = x + file_name.len();
screen.write_at( screen.write_at(
" ".repeat(screen.size.width - x).reverse().to_string(), buffer_kind.blue().bold().reverse().to_string(),
Coords::from(x, status_height), Coords::from(x, status_height),
); );
Ok(())
} }
let x = if editor.buffer.name.len() > 0 { x + file_name.len() } else { x + buffer_kind.len() };
screen.write_at(
" ".repeat(screen.size.width - x).reverse().to_string(),
Coords::from(x, status_height),
);
Ok(())
} }
} }

View File

@ -1,9 +1,9 @@
use crate::tui::utils; use crossterm::style::Stylize;
use crate::core::editor::Editor; use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen}; use crate::terminal::screen::{Coords, Screen};
use crossterm::style::Stylize; use crate::tui::utils;
pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { pub fn draw(screen: &mut Screen, editor: &Editor) {
// The welcome message // The welcome message
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name); let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
let message: [&str; 5] = [ let message: [&str; 5] = [
@ -11,7 +11,7 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
"", "",
"Type :help to open the README.md document", "Type :help to open the README.md document",
"Type :o <file> to open a file and edit", "Type :o <file> to open a file and edit",
"Type :q! or <C-c> to quit lambda", "Type :q! or <C-c> to quit the editor",
]; ];
// If the screen is big enough, we can draw // If the screen is big enough, we can draw
@ -20,14 +20,14 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
// Calculate where to place the title // Calculate where to place the title
let x = utils::calc_centered_x(screen.size.width, title.len()); let x = utils::calc_centred_x(screen.size.width, title.len());
// Write the title to the screen // Write the title to the screen
screen.write_at(title.yellow().to_string(), Coords::from(x, y)); screen.write_at(title.yellow().to_string(), Coords::from(x, y));
for line in message { for line in message {
// Each line has different width so requires a different x position to center it // Each line has different width so requires a different x position to center it
let x = utils::calc_centered_x(screen.size.width, line.len()) ; let x = utils::calc_centred_x(screen.size.width, line.len()) ;
// For each line we move downwards so increment y // For each line we move downwards so increment y
y += 1; y += 1;
@ -37,4 +37,3 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
} }
} }
} }

View File

@ -1,3 +1,3 @@
pub mod ui; pub mod ui;
pub mod components; pub mod utils;
mod utils; mod components;

View File

@ -1,60 +1,38 @@
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crate::core::editor::Editor; use crate::core::editor::Editor;
use crate::core::buffer::BufferKind;
use crate::terminal::screen::Screen; use crate::terminal::screen::Screen;
use crate::tui::components; use crate::tui::components;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
pub trait Component { pub fn start(screen: &mut Screen, editor: Editor) {
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()>; // Main screen loop, runs until the program exits
} loop {
// Refresh the screen
screen.refresh().unwrap();
struct Components { // Draw the welcome message only if it is a scratch buffer
bottom: Vec<Box<dyn Component>>, if editor.buffer.kind == BufferKind::Scratch {
} components::welcome::draw(screen, &editor);
};
pub struct Ui { // Draw the status bar
components: Components, components::statusbar::draw(screen, &editor).unwrap();
}
impl Ui { // Check for any key presses
pub fn new<'a>(editor: &'a Editor<'a>) -> Self { match read().unwrap() {
let status_bar = components::statusbar::StatusBar::new(editor); Event::Key(KeyEvent {
code: KeyCode::Char('q'),
Self { modifiers: KeyModifiers::CONTROL,
components: Components { bottom: vec![Box::new(status_bar)]}, ..
}) => break,
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
_ => (),
} }
} }
pub fn draw(&self) { screen.exit();
}
pub fn run(screen: &mut Screen, editor: Editor) {
loop {
// Refresh the screen
screen.refresh().unwrap();
// Generate all the UI elements
let components = Ui::new(&editor);
// Draw all UI elements
components.draw();
// Check for any key presses
match read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
_ => (),
}
}
screen.exit();
}
} }

View File

@ -3,12 +3,12 @@ pub fn with_spaces(text: &str) -> String {
format!(" {} ", text) format!(" {} ", text)
} }
// Calculates the x coordinate for centered text // Calculates the starting x coordinate for centred text
pub fn calc_centered_x(screen_width: usize, item_length: usize) -> usize { pub fn calc_centred_x(screen_width: usize, item_length: usize) -> usize {
(screen_width / 2) - (item_length / 2) (screen_width / 2) - (item_length / 2)
} }
// Finds and returns the longest element in a vector // Returns the longest element in a vector
pub fn longest_element_in_vec(elements: Vec<&str>) -> usize { pub fn longest_element_in_vec(elements: Vec<&str>) -> usize {
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len() elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
} }