emma
/
breed
Archived
forked from mars/breed
1
0
Fork 0

Compare commits

...

8 Commits

10 changed files with 513 additions and 149 deletions

10
.cargo/config.toml Normal file
View File

@ -0,0 +1,10 @@
[unstable]
build-std = [ "std", "panic_abort" ]
build-std-features = [ "panic_immediate_abort" ]
[profile.release]
strip = true # strip symbols from the binary
opt-level = "z" # optimize for size
lto = true # link time optimization
codegen-units = 1 # decrease parallelization
panic = "abort"

237
Cargo.lock generated
View File

@ -8,26 +8,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "arg"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "568bbed0b9810cd4b43c8560ff3c5d2318b29d78fc314818d156bbed7075d394"
dependencies = [
"arg-derive",
]
[[package]]
name = "arg-derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "849efc162c06e51ce911bf4fe702f62a08f3b6ebbbfc5178e86c6bae449c2c60"
dependencies = [
"quote",
"syn 1.0.109",
"tabwriter",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -75,9 +55,10 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
name = "breed"
version = "0.1.0"
dependencies = [
"arg",
"crossbeam-channel",
"crossterm",
"libc",
"notify",
"once_cell",
"parking_lot",
"ropey",
@ -127,6 +108,25 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.26.1"
@ -158,6 +158,18 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "filetime"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys 0.48.0",
]
[[package]]
name = "flate2"
version = "1.0.25"
@ -174,6 +186,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -196,12 +217,52 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "kqueue"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -279,7 +340,7 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
"windows-sys 0.45.0",
]
[[package]]
@ -292,6 +353,24 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "notify"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
dependencies = [
"bitflags",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"mio",
"walkdir",
"windows-sys 0.42.0",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@ -340,7 +419,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
"windows-sys 0.45.0",
]
[[package]]
@ -562,15 +641,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "tabwriter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.40"
@ -631,12 +701,6 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "walkdir"
version = "2.3.3"
@ -695,13 +759,37 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
@ -710,13 +798,28 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
@ -725,42 +828,84 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.1"

View File

@ -5,14 +5,15 @@ edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies]
arg = "0.4.1"
crossterm = "0.26"
crossbeam-channel = "0.5"
libc = "0.2.141"
once_cell = "1.17"
notify = "5"
parking_lot = "0.12"
ropey = "1.6"
toml = "0.7"
yacexits = "0.1.5"
once_cell = "1.17.1"
[dependencies.syntect]
version = "5"

2
rust-toolchain.toml Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

77
src/actions.rs Normal file
View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 Marceline Cramer
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::collections::HashMap;
use crate::{Direction, InsertState, Mode, State};
pub type Action = fn(&mut State);
pub fn load_actions() -> HashMap<String, Action> {
let list: &'static [(&'static str, fn(&mut State))] = &[
("move_char_left", move_char_left),
("move_line_down", move_line_down),
("move_line_up", move_line_up),
("move_char_right", move_char_right),
];
let mut actions = HashMap::with_capacity(list.len());
for (name, action) in list.iter() {
actions.insert(name.to_string(), *action);
}
actions
}
pub fn move_char_left(state: &mut State) {
state.move_cursor(Direction::Left);
}
pub fn move_char_right(state: &mut State) {
state.move_cursor(Direction::Right);
}
pub fn move_line_down(state: &mut State) {
state.move_cursor(Direction::Down);
}
pub fn move_line_up(state: &mut State) {
state.move_cursor(Direction::Up);
}
pub fn insert_mode(state: &mut State) {
state.mode = Mode::Insert(InsertState { append: false });
}
pub fn append_mode(state: &mut State) {
state.mode = Mode::Insert(InsertState { append: true });
}
pub fn open_below(state: &mut State) {
state.cursor.line += 1;
state.cursor.column = 0;
state.buffer.insert_char(state.cursor, '\n');
state.mode = Mode::Insert(InsertState { append: false });
}
pub fn open_above(state: &mut State) {
state.cursor.column = 0;
state.buffer.insert_char(state.cursor, '\n');
state.mode = Mode::Insert(InsertState { append: false });
}

View File

@ -36,6 +36,8 @@ pub struct Buffer {
pub text: Rope,
syntax_set: Arc<SyntaxSet>,
parser: ParseState,
style_dirty: bool,
style_generation: usize,
styled: Vec<Vec<(Style, Range<usize>)>>,
}
@ -51,6 +53,8 @@ impl Buffer {
text: Rope::from_str(text),
syntax_set: Arc::new(SyntaxSet::load_defaults_newlines()),
parser,
style_dirty: true,
style_generation: 0,
styled: Vec::new(),
};
@ -60,13 +64,15 @@ impl Buffer {
}
pub fn draw(
&self,
styles: &mut StyleStore,
&mut self,
cols: u16,
rows: u16,
scroll: Cursor,
out: &mut (impl ExecutableCommand + Write),
) -> crossterm::Result<u32> {
self.parse();
let mut styles = self.styles.lock();
let linenr_style = styles.get_scope("ui.linenr");
let linenr_width = self.text.len_lines().ilog10() + 1;
let gutter_width = linenr_width + 1;
@ -114,19 +120,23 @@ impl Buffer {
pub fn remove(&mut self, cursor: Cursor) {
let index = self.cursor_to_char(cursor);
self.text.remove(index..=index);
self.parse();
self.style_dirty = true;
}
pub fn insert_char(&mut self, cursor: Cursor, c: char) {
let index = self.cursor_to_char(cursor);
self.text.insert_char(index, c);
self.parse();
self.style_dirty = true;
}
/// Parses the whole file from scratch.
fn parse(&mut self) {
use std::fmt::Write;
let mut styles = self.styles.lock();
if styles.generation() == self.style_generation && !self.style_dirty {
return;
}
self.styled.clear();
let mut parser = self.parser.clone();
let mut line_buf = String::new();
@ -163,6 +173,9 @@ impl Buffer {
self.styled.push(line);
}
self.style_dirty = true;
self.style_generation = styles.generation();
}
pub fn clamped_cursor(&self, cursor: Cursor) -> Cursor {

143
src/config.rs Normal file
View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2023 Marceline Cramer
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crossbeam_channel::{unbounded, Receiver, RecvError, Sender};
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
use parking_lot::Mutex;
use crate::theme::{StyleStore, Theme};
pub struct Config {
/// Whether or not to wrap lines when moving the cursor left and right.
pub move_linewrap: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
move_linewrap: true,
}
}
}
/// Gets the configuration directory. Panics if unavailable.
pub fn get_config_dir() -> PathBuf {
std::env::var_os("HOME")
.map(PathBuf::try_from)
.expect("$HOME is unset")
.unwrap()
.join(".config/breed")
}
/// Watches a theme file and automatically reloads it into a [StyleStore].
pub struct ThemeWatcher {
styles: Arc<Mutex<StyleStore>>,
watcher: RecommendedWatcher,
current_path: PathBuf,
fs_rx: Receiver<notify::Result<Event>>,
command_rx: Receiver<PathBuf>,
quit: bool,
}
impl ThemeWatcher {
pub fn spawn(styles: Arc<Mutex<StyleStore>>) -> Sender<PathBuf> {
let themes_dir = get_config_dir().join("themes");
let default_path = themes_dir.join("default.toml");
let (fs_tx, fs_rx) = unbounded();
let (command_tx, command_rx) = unbounded();
let watcher = notify::recommended_watcher(fs_tx).unwrap();
let mut watcher = Self {
styles,
current_path: PathBuf::new(),
watcher,
fs_rx,
command_rx,
quit: false,
};
watcher.watch_theme(&default_path).unwrap();
std::thread::spawn(move || watcher.run());
command_tx
}
fn load_theme(&mut self, path: &Path) -> Result<(), String> {
let name = path.file_stem().unwrap_or_default().to_string_lossy();
let contents = std::fs::read_to_string(path)
.map_err(|err| format!("Failed to read theme file: {:?}", err))?;
let value =
toml::from_str(&contents).map_err(|err| format!("Failed to parse theme: {:?}", err))?;
let theme = Theme::from_value(&name, value)?;
self.styles.lock().apply(theme);
Ok(())
}
fn watch_theme(&mut self, path: &Path) -> notify::Result<()> {
let old = self.current_path.clone();
let mode = notify::RecursiveMode::NonRecursive;
self.watcher.watch(&path, mode)?;
self.current_path = path.to_path_buf();
if old != PathBuf::new() {
self.watcher.unwatch(&old)?;
}
self.load_theme(path).map_err(|err| notify::Error {
kind: notify::ErrorKind::Generic(err),
paths: vec![path.to_owned()],
})
}
fn on_fs(&mut self, event: Result<notify::Result<Event>, crossbeam_channel::RecvError>) {
match event {
Err(RecvError) => self.quit = true,
Ok(Ok(event)) => match event.kind {
EventKind::Modify(_) => {
for path in event.paths {
self.load_theme(&path).unwrap();
}
}
_ => {}
},
_ => {} // TODO display errors?
}
}
fn on_command(&mut self, path: Result<PathBuf, RecvError>) {
match path {
Err(RecvError) => self.quit = true,
Ok(path) => {
self.watch_theme(&path).unwrap();
}
}
}
fn run(mut self) {
while !self.quit {
crossbeam_channel::select! {
recv(self.fs_rx) -> event => self.on_fs(event),
recv(self.command_rx) -> path => self.on_command(path),
}
}
}
}

View File

@ -20,27 +20,25 @@
use std::{
env::args,
ffi::OsString,
fs::{ File, OpenOptions },
io::{
Read,
stdout,
Stdout,
Write,
},
fs::{File, OpenOptions},
io::{stdout, Read, Stdout, Write},
os::fd::FromRawFd,
path::{ Path },
path::{Path, PathBuf},
sync::Arc,
};
use crossbeam_channel::Sender;
use crossterm::{
cursor,
event::{read, Event, KeyCode, KeyEvent},
event::{Event, KeyCode, KeyEvent},
terminal, ExecutableCommand, Result,
};
use parking_lot::Mutex;
use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE};
mod actions;
mod buffer;
mod config;
mod theme;
use buffer::Buffer;
@ -53,23 +51,23 @@ pub struct Cursor {
}
#[derive(Clone, Debug, Default)]
struct NormalState {
pub struct NormalState {
pub error: Option<String>,
}
#[derive(Clone, Debug, Default)]
struct CommandState {
pub struct CommandState {
pub buf: String,
pub cursor: usize,
}
#[derive(Copy, Clone, Debug)]
struct InsertState {
pub struct InsertState {
append: bool,
}
#[derive(Clone, Debug)]
enum Mode {
pub enum Mode {
Normal(NormalState),
Command(CommandState),
Visual,
@ -102,7 +100,7 @@ pub enum Direction {
Right,
}
struct State {
pub struct State {
pub styles: Arc<Mutex<StyleStore>>,
pub buffer: Buffer,
pub cursor: Cursor,
@ -111,6 +109,7 @@ struct State {
pub scroll: Cursor,
pub size: (usize, usize),
pub quit: bool,
pub theme_tx: Sender<PathBuf>,
}
impl State {
@ -118,6 +117,7 @@ impl State {
let styles = Arc::new(Mutex::new(StyleStore::default()));
let buffer = Buffer::from_str(styles.clone(), text);
let (cols, rows) = terminal::size()?;
let theme_tx = config::ThemeWatcher::spawn(styles.clone());
Ok(Self {
styles,
@ -128,6 +128,7 @@ impl State {
scroll: Cursor::default(),
size: (cols as usize, rows as usize),
quit: false,
theme_tx,
})
}
@ -160,11 +161,12 @@ impl State {
_ => {}
}
// done with styles
drop(styles);
// draw buffer
let buffer_rows = if show_status_bar { rows - 1 } else { rows };
let lr_width = self
.buffer
.draw(&mut styles, cols, buffer_rows, self.scroll, out)?;
let lr_width = self.buffer.draw(cols, buffer_rows, self.scroll, out)?;
// draw cursor
let cursor_pos = set_cursor_pos.unwrap_or_else(|| {
@ -199,34 +201,16 @@ impl State {
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char('i') => {
let state = InsertState { append: false };
self.mode = Mode::Insert(state);
}
KeyCode::Char('a') => {
let state = InsertState { append: true };
self.move_cursor(Direction::Right);
self.mode = Mode::Insert(state);
}
KeyCode::Char('o') => {
let state = InsertState { append: false };
self.cursor.line += 1;
self.cursor.column = 0;
self.buffer.insert_char(self.cursor, '\n');
self.mode = Mode::Insert(state);
}
KeyCode::Char('O') => {
let state = InsertState { append: false };
self.cursor.column = 0;
self.buffer.insert_char(self.cursor, '\n');
self.mode = Mode::Insert(state);
}
KeyCode::Char(':') => {
self.mode = Mode::Command(Default::default());
}
KeyCode::Char('v') => {
self.mode = Mode::Visual;
}
KeyCode::Char('i') => actions::insert_mode(self),
KeyCode::Char('a') => actions::append_mode(self),
KeyCode::Char('o') => actions::open_below(self),
KeyCode::Char('O') => actions::open_above(self),
code => self.on_any_key(code),
},
event => self.on_any_event(event),
@ -332,10 +316,10 @@ impl State {
fn on_any_key(&mut self, code: KeyCode) {
match code {
KeyCode::Esc => self.mode = Mode::default(),
KeyCode::Char('h') | KeyCode::Left => self.move_cursor(Direction::Left),
KeyCode::Char('j') | KeyCode::Down => self.move_cursor(Direction::Down),
KeyCode::Char('k') | KeyCode::Up => self.move_cursor(Direction::Up),
KeyCode::Char('l') | KeyCode::Right => self.move_cursor(Direction::Right),
KeyCode::Char('h') | KeyCode::Left => actions::move_char_left(self),
KeyCode::Char('j') | KeyCode::Down => actions::move_line_down(self),
KeyCode::Char('k') | KeyCode::Up => actions::move_line_up(self),
KeyCode::Char('l') | KeyCode::Right => actions::move_char_right(self),
_ => {}
}
}
@ -349,44 +333,42 @@ impl State {
Ok(())
}
fn write_command(&mut self, command: &str, args: &[&str]) -> std::result::Result<(), String> {
let handle = match self.file.clone() {
Some(handle) => handle,
None => match args.get(0) {
Some(part) => OsString::from(part),
None => {
return Err(format!("{}: No file name.", command));
}
},
};
self.write_buffer(handle).map_err(|err| format!("{}", err))
}
fn execute_command(&mut self, command: &str) -> std::result::Result<(), String> {
let command_parts = command.split(' ').collect::<Vec<&str>>();
let commands = match command_parts.get(0) {
Some(parts) => parts.chars().collect::<Vec<char>>(),
let command = match command_parts.get(0) {
Some(command) => command,
None => return Ok(()),
};
for command in commands.clone().iter() {
match command {
'q' => self.quit = true,
'w' => {
let handle: OsString;
let args = &command_parts[1..];
if let Some(part) = self.file.clone() {
handle = part;
} else {
handle = match command_parts.get(1) {
Some(part) => OsString::from(part),
None => {
return Err(
format!("{}: No file name.", command)
);
},
};
}
self.write_buffer(handle).map_err(|err| {
format!("{}", err)
})?;
},
command => {
return Err(
format!("{}: Unrecognized command.", command)
);
},
match *command {
"q" => self.quit = true,
"w" => return self.write_command(command, args),
"wq" => {
self.write_command(command, args)?;
self.quit = true;
}
command => {
return Err(format!("{}: Unrecognized command.", command));
}
}
Ok(())
}
@ -408,10 +390,13 @@ impl State {
}
fn screen_main(stdout: &mut Stdout, mut state: State) -> Result<()> {
let poll_timeout = std::time::Duration::from_millis(100);
while !state.quit {
state.draw(stdout)?;
let event = read()?;
state.on_event(event);
if let true = crossterm::event::poll(poll_timeout)? {
let event = crossterm::event::read()?;
state.on_event(event);
}
}
Ok(())
@ -428,13 +413,13 @@ fn main() -> Result<()> {
let stdin = 0; // get stdin as a file descriptor
if unsafe { libc::isatty(stdin) } == 0 {
unsafe { File::from_raw_fd(stdin) }
} else { File::open("/dev/null").unwrap() }
},
} else {
File::open("/dev/null").unwrap()
}
}
Some(path) => {
let input_file = Path::new(path);
file_name = Some(OsString::from(
input_file.clone().display().to_string()
));
file_name = Some(OsString::from(input_file.clone().display().to_string()));
File::open(input_file).unwrap_or_else(|_| {
let mut err = String::new();
@ -446,8 +431,10 @@ fn main() -> Result<()> {
eprintln!("{}: {}: {}", argv[0], path, err);
exit(EX_UNAVAILABLE);
})
},
}.read_to_end(&mut buf).unwrap();
}
}
.read_to_end(&mut buf)
.unwrap();
let text = String::from_utf8(buf).unwrap_or_else(|_| {
eprintln!(

View File

@ -24,13 +24,7 @@ use crossterm::{style::Color, ExecutableCommand};
use once_cell::sync::Lazy;
use toml::{map::Map, Value};
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
let text = include_str!("../default_theme.toml");
let value = toml::from_str(text).expect("Failed to parse default theme");
Theme::from_value("default", value).expect("Failed to load default theme")
});
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct StyleStore {
/// The current theme.
theme: Theme,
@ -45,13 +39,6 @@ pub struct StyleStore {
styles: Vec<Style>,
}
impl Default for StyleStore {
fn default() -> Self {
let theme = Lazy::force(&DEFAULT_THEME).to_owned();
Self::new(theme)
}
}
impl StyleStore {
/// Creates a new style store with a given theme.
pub fn new(theme: Theme) -> Self {

View File

@ -9,7 +9,6 @@
"constructor" = "foam"
"comment" = { fg = "muted", modifiers = ["italic"]}
"constant" = "foam"
"constant.builtin" = "rose"