Compare commits
4 Commits
aa013fc422
...
6f81bab277
Author | SHA1 | Date |
---|---|---|
mars | 6f81bab277 | |
mars | 7d46cbcf9b | |
mars | b7f9c8ba2e | |
mars | 7fff70ce84 |
|
@ -76,8 +76,10 @@ name = "breed"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arg",
|
||||
"crossbeam-channel",
|
||||
"crossterm",
|
||||
"libc",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"ropey",
|
||||
|
@ -127,6 +129,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 +179,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 +207,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 +238,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 +361,7 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -292,6 +374,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 +440,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -695,13 +795,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 +834,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 +864,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"
|
||||
|
|
|
@ -7,7 +7,9 @@ license = "AGPL-3.0-or-later"
|
|||
[dependencies]
|
||||
arg = "0.4.1"
|
||||
crossterm = "0.26"
|
||||
crossbeam-channel = "0.5"
|
||||
libc = "0.2.141"
|
||||
notify = "5"
|
||||
once_cell = "1.17"
|
||||
parking_lot = "0.12"
|
||||
ropey = "1.6"
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
src/main.rs
68
src/main.rs
|
@ -28,19 +28,22 @@ use std::{
|
|||
Write,
|
||||
},
|
||||
os::fd::FromRawFd,
|
||||
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 +56,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 +105,7 @@ pub enum Direction {
|
|||
Right,
|
||||
}
|
||||
|
||||
struct State {
|
||||
pub struct State {
|
||||
pub styles: Arc<Mutex<StyleStore>>,
|
||||
pub buffer: Buffer,
|
||||
pub cursor: Cursor,
|
||||
|
@ -111,6 +114,7 @@ struct State {
|
|||
pub scroll: Cursor,
|
||||
pub size: (usize, usize),
|
||||
pub quit: bool,
|
||||
pub theme_tx: Sender<PathBuf>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -118,6 +122,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 +133,7 @@ impl State {
|
|||
scroll: Cursor::default(),
|
||||
size: (cols as usize, rows as usize),
|
||||
quit: false,
|
||||
theme_tx,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -160,11 +166,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 +206,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 +321,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),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -408,10 +397,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(())
|
||||
|
|
15
src/theme.rs
15
src/theme.rs
|
@ -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 {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
"constructor" = "foam"
|
||||
|
||||
|
||||
"comment" = { fg = "muted", modifiers = ["italic"]}
|
||||
"constant" = "foam"
|
||||
"constant.builtin" = "rose"
|
Loading…
Reference in New Issue