Compare commits
1 Commits
developmen
...
production
Author | SHA1 | Date | |
---|---|---|---|
b1078f17eb |
20
.replit
20
.replit
@ -1,20 +0,0 @@
|
|||||||
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"]
|
|
18
docs/design/introduction.md
Normal file
18
docs/design/introduction.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Introduction
|
||||||
|
Velocity is a fast and hackable text editor made for programming. It is split into two main parts, the backend and the frontend.
|
||||||
|
The backend, written in performant Rust, is the core of the editor and handles text manipulation, files, buffers, views, etc.
|
||||||
|
The frontend, which can be written in any language, is the UI of the editor and handles rendering, keybinds, etc.
|
||||||
|
|
||||||
|
## Editor
|
||||||
|
The `struct Editor` is the main class of the backend. It contains the current state of the editor, the views, and the configuration.
|
||||||
|
The `Editor` is the main way to interact with the backend, and is used by the frontend to manipulate the editor.
|
||||||
|
|
||||||
|
## View
|
||||||
|
The `struct View` defines a way of viewing a buffer, it contains information such as the current cursor locations, the scroll position, and the buffer
|
||||||
|
|
||||||
|
## Buffer
|
||||||
|
The `struct Buffer` contains the text of a file, and is the main way of interacting with the text. It contains a `Vec` of `Line`s, which contain a `Vec` of `char`s.
|
||||||
|
Buffers can be `BufferKind::Read`, meaning the file is read-only, or `BufferKind::Write` allowing edits to be made to the file, or can be `BufferKind::Scratch` which are not associated with a file.
|
||||||
|
|
||||||
|
## Line
|
||||||
|
The `struct Line` contains a `Vec` of `char`s, and is the main way of interacting with the text. It contains a `Vec` of `Line`s, which contain a `Vec` of `char`s.
|
@ -1,9 +0,0 @@
|
|||||||
{ pkgs }: {
|
|
||||||
deps = [
|
|
||||||
pkgs.rustc
|
|
||||||
pkgs.rustfmt
|
|
||||||
pkgs.cargo
|
|
||||||
pkgs.cargo-edit
|
|
||||||
pkgs.rust-analyzer
|
|
||||||
];
|
|
||||||
}
|
|
5
src/core.rs
Normal file
5
src/core.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod editor;
|
||||||
|
mod buffer;
|
||||||
|
mod languages;
|
||||||
|
mod config;
|
||||||
|
mod files;
|
@ -1,37 +1,61 @@
|
|||||||
use std::path::PathBuf;
|
use std::error::Error;
|
||||||
|
use crate::core::languages::Language;
|
||||||
|
use crate::core::files;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
enum BufferKind {
|
||||||
pub enum BufferKind {
|
|
||||||
Scratch,
|
|
||||||
Write,
|
|
||||||
Read,
|
Read,
|
||||||
|
Write,
|
||||||
|
Scratch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferKind {
|
impl BufferKind {
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
BufferKind::Scratch => "*scratch*",
|
|
||||||
BufferKind::Write => "write",
|
|
||||||
BufferKind::Read => "read",
|
BufferKind::Read => "read",
|
||||||
|
BufferKind::Write => "write",
|
||||||
|
BufferKind::Scratch => "*scratch*",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Buffer<'a> {
|
// An file that is open in the editor
|
||||||
pub data: Vec<String>,
|
pub struct Buffer {
|
||||||
pub path: PathBuf,
|
kind: BufferKind,
|
||||||
pub kind: BufferKind,
|
lines: Vec<String>,
|
||||||
pub name: &'a str,
|
filename: Option<String>,
|
||||||
|
language: Language,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Buffer<'a> {
|
impl Buffer {
|
||||||
pub fn new(path: PathBuf, name: &'a str, kind: BufferKind) -> Self {
|
pub fn new(path: Option<String>) -> Result<Self, Box<dyn Error>> {
|
||||||
// Return a buffer
|
// If the file exists, read it into the buffer
|
||||||
Self {
|
if let Some(path) = path {
|
||||||
data: vec![String::from("")],
|
// Read the file
|
||||||
path,
|
let lines = files::read_file(path.clone())?;
|
||||||
|
|
||||||
|
// Get the language
|
||||||
|
let language = Language::get_language(&path);
|
||||||
|
|
||||||
|
// Check if the file is writeable
|
||||||
|
let kind = if std::path::Path::new(&path).is_file() {
|
||||||
|
BufferKind::Write
|
||||||
|
} else {
|
||||||
|
BufferKind::Read
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
kind,
|
kind,
|
||||||
name,
|
lines,
|
||||||
|
filename: Some(path),
|
||||||
|
language,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
kind: BufferKind::Scratch,
|
||||||
|
lines: Vec::new(),
|
||||||
|
filename: None,
|
||||||
|
language: Language::PlainText,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
19
src/core/config.rs
Normal file
19
src/core/config.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
name: &'static str,
|
||||||
|
version: &'static str,
|
||||||
|
logo: &'static str,
|
||||||
|
motto: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Velocity",
|
||||||
|
version: VERSION.unwrap_or("UNKNOWN"),
|
||||||
|
logo: "λ",
|
||||||
|
motto: "Hackable text editor for nerds.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +1,22 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use crate::core::buffer::Buffer;
|
use crate::core::buffer::Buffer;
|
||||||
use crate::core::buffer::BufferKind;
|
use crate::core::config::Config;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
pub struct Config<'a> {
|
pub struct Editor {
|
||||||
pub logo: &'a str,
|
buffers: Vec<Buffer>,
|
||||||
pub friendly_name: &'a str,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Config<'a> {
|
impl Editor {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
logo: "λ",
|
buffers: Vec::new(),
|
||||||
friendly_name: "Velocity",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum Mode {
|
|
||||||
Normal,
|
|
||||||
Insert,
|
|
||||||
Select,
|
|
||||||
Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mode {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Mode::Normal => "NORMAL",
|
|
||||||
Mode::Insert => "INSERT",
|
|
||||||
Mode::Select => "SELECT",
|
|
||||||
Mode::Command => "COMMAND",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Editor<'a> {
|
|
||||||
pub config: Config<'a>,
|
|
||||||
pub buffer: Box<Buffer<'a>>,
|
|
||||||
pub cursors: Vec<i32>,
|
|
||||||
pub mode: Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Editor<'a> {
|
|
||||||
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 {
|
|
||||||
config: Config::new(),
|
config: Config::new(),
|
||||||
buffer: Box::new(Buffer::new(path, buffer_name, buffer_kind)),
|
|
||||||
cursors: Vec::from([0]),
|
|
||||||
mode: Mode::Normal,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attach_file(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
self.buffers.push(Buffer::new(None)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
9
src/core/files.rs
Normal file
9
src/core/files.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub fn read_file(path: String) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
|
let contents: String = std::fs::read_to_string(path)?;
|
||||||
|
let lines: Vec<String> = contents.lines()
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.collect();
|
||||||
|
Ok(lines)
|
||||||
|
}
|
31
src/core/languages.rs
Normal file
31
src/core/languages.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
pub enum Language {
|
||||||
|
Rust,
|
||||||
|
Python,
|
||||||
|
Markdown,
|
||||||
|
PlainText,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Language {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Language::Rust => "rust",
|
||||||
|
Language::Python => "python",
|
||||||
|
Language::Markdown => "markdown",
|
||||||
|
Language::PlainText => "plain text",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_language(filename: &str) -> Self {
|
||||||
|
let extension = std::path::Path::new(filename)
|
||||||
|
.extension()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
match extension {
|
||||||
|
"rs" => Language::Rust,
|
||||||
|
"py" => Language::Python,
|
||||||
|
"md" => Language::Markdown,
|
||||||
|
_ => Language::PlainText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
pub mod editor;
|
|
||||||
pub mod buffer;
|
|
23
src/main.rs
23
src/main.rs
@ -1,25 +1,6 @@
|
|||||||
mod core;
|
mod core;
|
||||||
mod terminal;
|
|
||||||
mod tui;
|
|
||||||
use std::{env, path::PathBuf};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Collect command line arguments
|
let mut editor = core::editor::Editor::new();
|
||||||
let args: Vec<String> = env::args().collect();
|
editor.attach_file().unwrap();
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
// Begin lambda
|
|
||||||
tui::ui::start(&mut screen, velocity);
|
|
||||||
}
|
}
|
@ -1 +0,0 @@
|
|||||||
pub mod screen;
|
|
@ -1,154 +0,0 @@
|
|||||||
use crossterm::cursor::{Hide, MoveTo, Show};
|
|
||||||
use crossterm::style::Print;
|
|
||||||
use crossterm::terminal;
|
|
||||||
use crossterm::{execute, ErrorKind};
|
|
||||||
use std::io::stdout;
|
|
||||||
|
|
||||||
// Struct for holding coordinates
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Coords {
|
|
||||||
pub x: usize,
|
|
||||||
pub y: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating a coordinates from two values
|
|
||||||
impl Coords {
|
|
||||||
pub fn from(x: usize, y: usize) -> Self {
|
|
||||||
Self { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A cursor for writing to the terminal screen
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Cursor {
|
|
||||||
pub position: Coords,
|
|
||||||
pub hidden: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a cursor is created
|
|
||||||
impl Cursor {
|
|
||||||
pub fn new() -> Result<Self, ErrorKind> {
|
|
||||||
let mut cursor = Self {
|
|
||||||
position: Coords::from(0, 0),
|
|
||||||
hidden: true,
|
|
||||||
};
|
|
||||||
Cursor::move_to(&mut cursor, Coords::from(0, 0));
|
|
||||||
Cursor::hide(&mut cursor);
|
|
||||||
Ok(cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor methods
|
|
||||||
impl Cursor {
|
|
||||||
pub fn move_to(&mut self, position: Coords) {
|
|
||||||
// Set the new position of the cursor
|
|
||||||
self.position = position;
|
|
||||||
|
|
||||||
// Move the cursor to the desired posiition in the terminal
|
|
||||||
execute!(stdout(), MoveTo(position.x as u16, position.y as u16)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hide(&mut self) {
|
|
||||||
// Remember that the cursor is hidden
|
|
||||||
self.hidden = true;
|
|
||||||
|
|
||||||
// Hide the cursor from the terminal screen
|
|
||||||
execute!(stdout(), Hide).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(&mut self) {
|
|
||||||
// Remember that the cursor isn't hidden
|
|
||||||
self.hidden = false;
|
|
||||||
|
|
||||||
// Show the cursor to the terminal screen
|
|
||||||
execute!(stdout(), Show).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A struct for holding the size of the terminal
|
|
||||||
pub struct Size {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The terminal screen
|
|
||||||
pub struct Screen {
|
|
||||||
pub size: Size,
|
|
||||||
pub cursor: Cursor,
|
|
||||||
}
|
|
||||||
|
|
||||||
// For when a new terminal screen is created
|
|
||||||
impl Screen {
|
|
||||||
pub fn new() -> Result<Self, ErrorKind> {
|
|
||||||
// Get the size of the terminal
|
|
||||||
let size = terminal::size()?;
|
|
||||||
|
|
||||||
// Define a new terminal screen struct
|
|
||||||
let mut screen = Self {
|
|
||||||
size: Size {
|
|
||||||
width: size.0 as usize,
|
|
||||||
height: size.1 as usize,
|
|
||||||
},
|
|
||||||
cursor: Cursor::new().unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Empty the terminal screen
|
|
||||||
Screen::clear();
|
|
||||||
|
|
||||||
// Enter the terminal screen
|
|
||||||
screen.enter();
|
|
||||||
|
|
||||||
// Return a result containing the terminal
|
|
||||||
Ok(screen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminal functions and methods for managing the terminal
|
|
||||||
impl Screen {
|
|
||||||
pub fn refresh(&mut self) -> Result<(), ErrorKind>{
|
|
||||||
// Clear the screen
|
|
||||||
Screen::clear();
|
|
||||||
|
|
||||||
// Update the screen dimensions
|
|
||||||
let size = terminal::size()?;
|
|
||||||
self.size.width = size.0 as usize;
|
|
||||||
self.size.height = size.1 as usize;
|
|
||||||
|
|
||||||
// Return Ok if was successful
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter(&mut self) {
|
|
||||||
// Hide the cursor
|
|
||||||
self.cursor.hide();
|
|
||||||
|
|
||||||
// Enter the terminal screen
|
|
||||||
terminal::enable_raw_mode().unwrap();
|
|
||||||
execute!(stdout(), terminal::EnterAlternateScreen).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit(&mut self) {
|
|
||||||
// Show the cursor
|
|
||||||
self.cursor.show();
|
|
||||||
|
|
||||||
// Exit the terminal screen
|
|
||||||
execute!(stdout(), terminal::LeaveAlternateScreen).unwrap();
|
|
||||||
terminal::disable_raw_mode().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear() {
|
|
||||||
// Clears the terminal screen
|
|
||||||
execute!(stdout(), terminal::Clear(terminal::ClearType::All)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(text: &str) {
|
|
||||||
// Writes a line to a current cursor position
|
|
||||||
execute!(stdout(), Print(text)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_at(&mut self, text: String, position: Coords) {
|
|
||||||
// Writes a line at a set of coordinates
|
|
||||||
self.cursor.move_to(position);
|
|
||||||
Screen::write(&text);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod statusbar;
|
|
||||||
pub mod welcome;
|
|
@ -1,63 +0,0 @@
|
|||||||
use crossterm::style::Stylize;
|
|
||||||
use crate::core::editor::Editor;
|
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
|
||||||
use crate::tui::utils;
|
|
||||||
|
|
||||||
pub fn draw(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 = &utils::with_spaces(editor.config.logo) as &str;
|
|
||||||
// Get the current mode into a string
|
|
||||||
let mode_string = &utils::with_spaces(editor.mode.as_str()) as &str;
|
|
||||||
// Get the current open file 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;
|
|
||||||
|
|
||||||
// Calculate the total length of all the status bar components
|
|
||||||
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + buffer_kind.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),
|
|
||||||
);
|
|
||||||
|
|
||||||
let x = x + mode_string.len();
|
|
||||||
// Draws the file name if it has a length, if not then it will draw the buffer type
|
|
||||||
if editor.buffer.name.len() > 0 {
|
|
||||||
screen.write_at(
|
|
||||||
file_name.magenta().bold().reverse().to_string(),
|
|
||||||
Coords::from(x, status_height),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
screen.write_at(
|
|
||||||
buffer_kind.blue().bold().reverse().to_string(),
|
|
||||||
Coords::from(x, status_height),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
use crossterm::style::Stylize;
|
|
||||||
use crate::core::editor::Editor;
|
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
|
||||||
use crate::tui::utils;
|
|
||||||
|
|
||||||
pub fn draw(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 the editor",
|
|
||||||
];
|
|
||||||
|
|
||||||
// If the screen is big enough, we can draw
|
|
||||||
if screen.size.width > utils::longest_element_in_vec(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 = utils::calc_centred_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 = utils::calc_centred_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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
pub mod ui;
|
|
||||||
pub mod utils;
|
|
||||||
mod components;
|
|
@ -1,38 +0,0 @@
|
|||||||
use crate::core::editor::Editor;
|
|
||||||
use crate::core::buffer::BufferKind;
|
|
||||||
use crate::terminal::screen::Screen;
|
|
||||||
use crate::tui::components;
|
|
||||||
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
|
|
||||||
pub fn start(screen: &mut Screen, editor: Editor) {
|
|
||||||
// Main screen loop, runs until the program exits
|
|
||||||
loop {
|
|
||||||
// Refresh the screen
|
|
||||||
screen.refresh().unwrap();
|
|
||||||
|
|
||||||
// Draw the welcome message only if it is a scratch buffer
|
|
||||||
if editor.buffer.kind == BufferKind::Scratch {
|
|
||||||
components::welcome::draw(screen, &editor);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw the status bar
|
|
||||||
components::statusbar::draw(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();
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
// Surrounds a &str with spaces
|
|
||||||
pub fn with_spaces(text: &str) -> String {
|
|
||||||
format!(" {} ", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates the starting x coordinate for centred text
|
|
||||||
pub fn calc_centred_x(screen_width: usize, item_length: usize) -> usize {
|
|
||||||
(screen_width / 2) - (item_length / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the longest element in a vector
|
|
||||||
pub fn longest_element_in_vec(elements: Vec<&str>) -> usize {
|
|
||||||
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user