diff --git a/.circleci/config.yml b/.circleci/config.yml index 39d48c1..c9d759f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,6 +9,7 @@ jobs: environment: CODECOV_TOKEN: "e58da505-19f2-481c-8068-e845cb36fbe4" TZ: "/usr/share/zoneinfo/Europe/Paris" + rider-config: "1" steps: - checkout @@ -31,15 +32,23 @@ jobs: command: | rustup run nightly rustc --version --verbose rustup run nightly cargo --version --verbose - rustup run nightly cargo build + rustup run nightly cargo build --all + mkdir -p ~/.local/bin + cp $(pwd)/target/debug/rider-* $HOME/.local/bin + export XDG_BIN_HOME=$HOME/.local/bin + - run: + name: Run rider-generator + command: | + export XDG_RUNTIME_DIR=$(pwd) + export XDG_BIN_HOME=$HOME/.local/bin + rustup run nightly cargo run -p rider-generator - run: name: Test and code coverage command: | - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - Xvfb :5 -screen 0 800x600x24 +extension GLX +render -noreset & - export DISPLAY=:5 + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin || echo 0 export XDG_RUNTIME_DIR=$(pwd) - rustup run nightly cargo tarpaulin --ciserver circle-ci --out Xml + export XDG_BIN_HOME=$HOME/.local/bin + rustup run nightly cargo tarpaulin --all --ciserver circle-ci --out Xml - run: name: Upload Coverage command: | diff --git a/Cargo.lock b/Cargo.lock index 1f75337..d4a8fc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.6.9" @@ -413,13 +415,24 @@ dependencies = [ [[package]] name = "rider" version = "0.1.0" +dependencies = [ + "rider-config 0.1.0", + "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rider-config" +version = "0.1.0" dependencies = [ "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rider-lexers 0.1.0", + "rider-themes 0.1.0", "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", @@ -427,6 +440,60 @@ dependencies = [ "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rider-editor" +version = "0.1.0" +dependencies = [ + "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rider-config 0.1.0", + "rider-lexers 0.1.0", + "rider-themes 0.1.0", + "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rider-generator" +version = "0.1.0" +dependencies = [ + "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rider-config 0.1.0", + "rider-themes 0.1.0", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rider-lexers" +version = "0.1.0" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rider-themes" +version = "0.1.0" +dependencies = [ + "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.13" @@ -590,6 +657,14 @@ name = "utf8-ranges" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uuid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vec_map" version = "0.6.0" @@ -710,6 +785,7 @@ dependencies = [ "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6" "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" diff --git a/Cargo.toml b/Cargo.toml index 6fd482d..24dafda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,17 +4,25 @@ version = "0.1.0" authors = ["Adrian Wozniak "] edition = "2018" +[workspace] +members = [ + "rider-generator", + "rider-config", + "rider-themes", + "rider-lexers", + "rider-editor" +] +default-members = [ + "rider-generator", + "rider-config", + "rider-themes", + "rider-lexers", + "rider-editor" +] + [dependencies] -rand = "0.5" -plex = "*" -dirs = "*" -serde = "*" -serde_json = "*" -serde_derive = "*" -log = "*" -env_logger = "*" -simplelog = "*" -lazy_static = "*" +rider-config = { version = "*", path = "./rider-config" } +uuid = { version = "0.7", features = ["v4"] } [dependencies.sdl2] version = "0.31.0" diff --git a/assets/gear-64x64.png b/assets/gear-64x64.png deleted file mode 100644 index 0911c7a..0000000 Binary files a/assets/gear-64x64.png and /dev/null differ diff --git a/assets/gear-64x64.raw b/assets/gear-64x64.raw deleted file mode 100644 index 21b478d..0000000 Binary files a/assets/gear-64x64.raw and /dev/null differ diff --git a/assets/gear.jpg b/assets/gear.jpg deleted file mode 100644 index 5456a99..0000000 Binary files a/assets/gear.jpg and /dev/null differ diff --git a/assets/theme.txt b/assets/theme.txt deleted file mode 100644 index 86e0fdb..0000000 --- a/assets/theme.txt +++ /dev/null @@ -1,53 +0,0 @@ -```railscasts - -``comment -#BC9358 -italic - -``escaped character -#509E4F - -``class -#FFF - -``constant -#FFF - -``float -#A4C260 - -``function -#FFC56D - -``global variable -#D0CFFE - -``integer -#A4C260 - -``inline code -#151515 - -``instance variable -#D0CFFE - -``doctype -#E7BE69 - -``keyword -#CB7832 - -``regex -#A4C260 - -``string -#A4C260 - -``symbol -#6C9CBD - -``html tag -#E7BE69 - -``boolean -#6C9CBD diff --git a/rider-config/Cargo.toml b/rider-config/Cargo.toml new file mode 100644 index 0000000..3efe103 --- /dev/null +++ b/rider-config/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rider-config" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +rider-lexers = { path = "../rider-lexers", version = "0.1.0" } +rider-themes = { path = "../rider-themes", version = "0.1.0" } +rand = "0.5" +plex = "*" +dirs = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +log = "*" +env_logger = "*" +simplelog = "*" +lazy_static = "*" + +[dependencies.sdl2] +version = "0.31.0" +features = ["gfx", "image", "mixer", "ttf"] diff --git a/rider-config/src/config.rs b/rider-config/src/config.rs new file mode 100644 index 0000000..ba417e9 --- /dev/null +++ b/rider-config/src/config.rs @@ -0,0 +1,242 @@ +use crate::directories::*; +use crate::EditorConfig; +use crate::ScrollConfig; +use rider_lexers::Language; +use rider_themes::Theme; +use std::collections::HashMap; +use std::fs; + +pub type LanguageMapping = HashMap; + +#[derive(Debug, Clone)] +pub struct Config { + width: u32, + height: u32, + menu_height: u16, + editor_config: EditorConfig, + theme: Theme, + extensions_mapping: LanguageMapping, + scroll: ScrollConfig, + directories: Directories, +} + +impl Config { + pub fn new() -> Self { + let directories = Directories::new(None, None); + let editor_config = EditorConfig::new(&directories); + let mut extensions_mapping = HashMap::new(); + extensions_mapping.insert(".".to_string(), Language::PlainText); + extensions_mapping.insert("txt".to_string(), Language::PlainText); + extensions_mapping.insert("rs".to_string(), Language::Rust); + + Self { + width: 1024, + height: 860, + menu_height: 60, + theme: Theme::default(), + editor_config, + extensions_mapping, + scroll: ScrollConfig::new(), + directories, + } + } + + pub fn width(&self) -> u32 { + self.width + } + + pub fn set_width(&mut self, w: u32) { + self.width = w; + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn set_height(&mut self, h: u32) { + self.height = h; + } + + pub fn editor_config(&self) -> &EditorConfig { + &self.editor_config + } + + pub fn theme(&self) -> &Theme { + &self.theme + } + + pub fn menu_height(&self) -> u16 { + self.menu_height + } + + pub fn editor_top_margin(&self) -> i32 { + i32::from(self.menu_height()) + i32::from(self.editor_config().margin_top()) + } + + pub fn editor_left_margin(&self) -> i32 { + i32::from(self.editor_config().margin_left()) + } + + pub fn extensions_mapping(&self) -> &LanguageMapping { + &self.extensions_mapping + } + + pub fn scroll(&self) -> &ScrollConfig { + &self.scroll + } + + pub fn scroll_mut(&mut self) -> &mut ScrollConfig { + &mut self.scroll + } + + pub fn directories(&self) -> &Directories { + &self.directories + } + + pub fn set_theme(&mut self, theme: String) { + self.theme = self.load_theme(theme); + } +} + +impl Config { + pub fn load_theme(&self, theme_name: String) -> Theme { + let home_dir = dirs::config_dir().unwrap(); + let mut config_dir = home_dir.clone(); + config_dir.push("rider"); + fs::create_dir_all(&config_dir) + .unwrap_or_else(|_| panic!("Cannot create config directory")); + self.load_theme_content(format!("{}.json", theme_name).as_str()) + } + + fn load_theme_content(&self, file_name: &str) -> Theme { + let mut config_file = self.directories.themes_dir.clone(); + config_file.push(file_name); + let contents = match fs::read_to_string(&config_file) { + Ok(s) => s, + Err(_) => fs::read_to_string(&config_file).unwrap_or_else(|_| "".to_owned()), + }; + serde_json::from_str(&contents).unwrap_or_default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn must_return_language_mapping() { + let config = Config::new(); + + let mapping = config.extensions_mapping(); + { + let mut keys: Vec = mapping.keys().map(|s| s.to_string()).collect(); + let mut expected: Vec = + vec![".".to_string(), "txt".to_string(), "rs".to_string()]; + keys.sort(); + expected.sort(); + assert_eq!(keys, expected); + } + { + let mut keys: Vec = mapping.values().map(|s| s.clone()).collect(); + let mut expected: Vec = + vec![Language::PlainText, Language::PlainText, Language::Rust]; + keys.sort(); + expected.sort(); + assert_eq!(keys, expected); + } + } + + #[test] + fn assert_scroll() { + let config = Config::new(); + let result = config.scroll(); + let expected = ScrollConfig::new(); + assert_eq!(result.clone(), expected); + } + + #[test] + fn assert_scroll_mut() { + let mut config = Config::new(); + let result = config.scroll_mut(); + result.set_margin_right(1236); + let mut expected = ScrollConfig::new(); + expected.set_margin_right(1236); + assert_eq!(result.clone(), expected); + } +} + +#[cfg(test)] +mod test_getters { + use super::*; + + #[test] + fn assert_width() { + let config = Config::new(); + let result = config.width(); + let expected = 1024; + assert_eq!(result, expected); + } + + #[test] + fn assert_height() { + let config = Config::new(); + let result = config.height(); + let expected = 860; + assert_eq!(result, expected); + } + + // #[test] + // fn assert_editor_config() { + // let config = Config::new(); + // let result = config.editor_config(); + // let expected = 1; + // assert_eq!(result, expected); + // } + + // #[test] + // fn assert_theme() { + // let config = Config::new(); + // let result = config.theme(); + // let expected = 1; + // assert_eq!(result, expected); + // } + + #[test] + fn assert_menu_height() { + let config = Config::new(); + let result = config.menu_height(); + let expected = 60; + assert_eq!(result, expected); + } + + #[test] + fn assert_editor_top_margin() { + let config = Config::new(); + let result = config.editor_top_margin(); + let expected = config.menu_height() as i32 + config.editor_config().margin_top() as i32; + assert_eq!(result, expected); + } + + #[test] + fn assert_editor_left_margin() { + let config = Config::new(); + let result = config.editor_left_margin(); + let expected = 10; + assert_eq!(result, expected); + } + + #[test] + fn assert_extensions_mapping() { + let config = Config::new(); + let mut result: Vec = config + .extensions_mapping() + .keys() + .map(|s| s.to_owned()) + .collect(); + result.sort(); + let mut expected: Vec = vec!["rs".to_string(), "txt".to_string(), ".".to_string()]; + expected.sort(); + assert_eq!(result, expected); + } + +} diff --git a/rider-config/src/directories.rs b/rider-config/src/directories.rs new file mode 100644 index 0000000..7091c2a --- /dev/null +++ b/rider-config/src/directories.rs @@ -0,0 +1,183 @@ +use dirs; +use std::env; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +pub struct Directories { + pub log_dir: PathBuf, + pub themes_dir: PathBuf, + pub fonts_dir: PathBuf, + pub config_dir: PathBuf, + pub project_dir: PathBuf, +} + +impl Directories { + pub fn new(config_dir: Option, project_dir: Option) -> Self { + let path = match config_dir { + Some(s) => s, + None => dirs::config_dir().unwrap().to_str().unwrap().to_owned(), + }; + let mut config_dir = PathBuf::new(); + config_dir.push(path); + config_dir.push("rider"); + + let path = match project_dir { + Some(s) => s, + None => dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(), + }; + let mut project_dir = PathBuf::new(); + project_dir.push(path); + project_dir.push(".rider"); + + Self { + log_dir: log_dir(&config_dir), + themes_dir: themes_dir(&config_dir), + fonts_dir: fonts_dir(&config_dir), + config_dir, + project_dir, + } + } +} + +pub fn log_dir(config_dir: &PathBuf) -> PathBuf { + let path = config_dir.to_str().unwrap().to_owned(); + let mut path_buf = PathBuf::new(); + path_buf.push(path); + path_buf.push("log"); + path_buf +} + +pub fn themes_dir(config_dir: &PathBuf) -> PathBuf { + let path = config_dir.to_str().unwrap().to_owned(); + let mut path_buf = PathBuf::new(); + path_buf.push(path); + path_buf.push("themes"); + path_buf +} + +pub fn fonts_dir(config_dir: &PathBuf) -> PathBuf { + let path = config_dir.to_str().unwrap().to_owned(); + let mut path_buf = PathBuf::new(); + path_buf.push(path); + path_buf.push("fonts"); + path_buf +} + +pub fn project_dir() -> PathBuf { + let path = dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(); + let mut path_buf = PathBuf::new(); + path_buf.push(path); + path_buf.push(".rider"); + path_buf +} + +#[cfg_attr(tarpaulin, skip)] +pub fn binaries_directory() -> Result { + let mut exec_dir = PathBuf::new(); + exec_dir.push(dirs::executable_dir().unwrap().clone()); + let mut rider_editor = exec_dir.clone(); + rider_editor.push("rider-editor"); + if rider_editor.exists() { + return Ok(exec_dir); + } + + let path = dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(); + let mut path_buf = PathBuf::new(); + path_buf.push(path.clone()); + path_buf.push("rider-editor"); + if path_buf.exists() { + let mut path_buf = PathBuf::new(); + path_buf.push(path); + return Ok(path_buf); + } + + let mut current_dir = env::current_dir().unwrap(); + current_dir.push("target"); + current_dir.push("debug"); + let mut rider_editor = current_dir.clone(); + rider_editor.push("rider-editor"); + if rider_editor.exists() { + return Ok(current_dir); + } + + let executable = dirs::executable_dir().unwrap(); + let mut rider_editor = executable.clone(); + rider_editor.push("rider-editor"); + if rider_editor.exists() { + return Ok(executable); + } + + Err("Cannot find binaries!".to_string()) +} + +pub fn get_binary_path(name: &str) -> Result { + if cfg!(test) { + use std::fs; + println!("#[cfg(test)]"); + + let mut current_dir = env::current_dir().unwrap(); + current_dir.push("target"); + current_dir.push("debug"); + let name = name.to_string().to_lowercase().replace("-", "_"); + println!(" name {:?}", name); + current_dir.push(vec![name.clone(), "*".to_string()].join("-")); + for entry in fs::read_dir(current_dir.to_str().unwrap()).unwrap() { + if let Ok(entry) = entry { + if let Ok(meta) = entry.metadata() { + if meta.is_file() && !entry.path().ends_with(".d") { + return Ok(entry.path().to_str().unwrap().to_string()); + } + } + } + } + Err(format!("Cannot find {:?}", name)) + } else { + println!("#[cfg(not(test))]"); + let r = binaries_directory(); + let mut binaries: PathBuf = r.unwrap_or_else(|e| panic!(e)); + binaries.push(name.to_string()); + println!(" name {}", name); + match binaries.to_str() { + Some(s) => Ok(s.to_owned()), + _ => Err(format!("Cannot find {:?}", name)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::{Path, PathBuf}; + + #[test] + fn assert_log_dir() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let path = directories.log_dir.clone(); + let expected: PathBuf = Path::new("/tmp/rider/log").into(); + assert_eq!(path, expected); + } + + #[test] + fn assert_themes_dir() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let path = directories.themes_dir.clone(); + let expected: PathBuf = Path::new("/tmp/rider/themes").into(); + assert_eq!(path, expected); + } + + #[test] + fn assert_fonts_dir() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let path = directories.fonts_dir.clone(); + let expected: PathBuf = Path::new("/tmp/rider/fonts").into(); + assert_eq!(path, expected); + } + + #[test] + fn assert_config_dir() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let path = directories.config_dir.clone(); + let expected: PathBuf = Path::new("/tmp/rider").into(); + assert_eq!(path, expected); + } +} diff --git a/rider-config/src/editor_config.rs b/rider-config/src/editor_config.rs new file mode 100644 index 0000000..5903cd1 --- /dev/null +++ b/rider-config/src/editor_config.rs @@ -0,0 +1,93 @@ +use crate::directories::Directories; + +#[derive(Debug, Clone)] +pub struct EditorConfig { + character_size: u16, + font_path: String, + current_theme: String, + margin_left: u16, + margin_top: u16, +} + +impl EditorConfig { + pub fn new(directories: &Directories) -> Self { + let mut default_font_path = directories.fonts_dir.clone(); + default_font_path.push("DejaVuSansMono.ttf"); + Self { + character_size: 14, + font_path: default_font_path.to_str().unwrap().to_string(), + current_theme: "railscasts".to_string(), + margin_left: 10, + margin_top: 10, + } + } + + pub fn character_size(&self) -> u16 { + self.character_size + } + + pub fn font_path(&self) -> &String { + &self.font_path + } + + pub fn current_theme(&self) -> &String { + &self.current_theme + } + + pub fn margin_left(&self) -> u16 { + self.margin_left + } + + pub fn margin_top(&self) -> u16 { + self.margin_top + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn assert_font_path() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let config = EditorConfig::new(&directories); + let path = config.font_path().to_owned(); + let expected: String = "/tmp/rider/fonts/DejaVuSansMono.ttf".to_owned(); + assert_eq!(path, expected); + } + + #[test] + fn assert_character_size() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let config = EditorConfig::new(&directories); + let result = config.character_size(); + let expected: u16 = 14; + assert_eq!(result, expected); + } + + #[test] + fn assert_current_theme() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let config = EditorConfig::new(&directories); + let result = config.current_theme().to_owned(); + let expected = "railscasts".to_owned(); + assert_eq!(result, expected); + } + + #[test] + fn assert_margin_left() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let config = EditorConfig::new(&directories); + let result = config.margin_left(); + let expected: u16 = 10; + assert_eq!(result, expected); + } + + #[test] + fn assert_margin_top() { + let directories = Directories::new(Some("/tmp".to_owned()), None); + let config = EditorConfig::new(&directories); + let result = config.margin_top(); + let expected: u16 = 10; + assert_eq!(result, expected); + } +} diff --git a/src/config/mod.rs b/rider-config/src/lib.rs similarity index 56% rename from src/config/mod.rs rename to rider-config/src/lib.rs index 35a7e4a..05bc7a2 100644 --- a/src/config/mod.rs +++ b/rider-config/src/lib.rs @@ -1,15 +1,17 @@ +extern crate rider_lexers; +extern crate rider_themes; + use std::sync::{Arc, RwLock}; pub mod config; -pub(crate) mod creator; pub mod directories; pub mod editor_config; pub mod scroll_config; -pub use crate::config::config::*; -pub use crate::config::directories::*; -pub use crate::config::editor_config::*; -pub use crate::config::scroll_config::*; +pub use crate::config::*; +pub use crate::directories::*; +pub use crate::editor_config::*; +pub use crate::scroll_config::*; pub type ConfigAccess = Arc>; diff --git a/src/config/scroll_config.rs b/rider-config/src/scroll_config.rs similarity index 91% rename from src/config/scroll_config.rs rename to rider-config/src/scroll_config.rs index 9669b4a..3f4149b 100644 --- a/src/config/scroll_config.rs +++ b/rider-config/src/scroll_config.rs @@ -39,6 +39,17 @@ impl ScrollConfig { } } +impl Default for ScrollConfig { + fn default() -> Self { + Self { + width: 4, + margin_right: 5, + speed: 10, + } + } +} + +#[cfg(test)] mod tests { use super::*; @@ -92,5 +103,4 @@ mod tests { let expected = 98; assert_eq!(result, expected); } - } diff --git a/rider-editor/Cargo.toml b/rider-editor/Cargo.toml new file mode 100644 index 0000000..f5541e1 --- /dev/null +++ b/rider-editor/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rider-editor" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +rider-config = { path = "../rider-config", version = "0.1.0" } +rider-lexers = { path = "../rider-lexers", version = "0.1.0" } +rider-themes = { path = "../rider-themes", version = "0.1.0" } +rand = "0.5" +dirs = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +log = "*" +simplelog = "*" +lazy_static = "*" + +[dependencies.sdl2] +version = "0.31.0" +features = ["gfx", "image", "mixer", "ttf"] diff --git a/assets/gear-64x64.bmp b/rider-editor/assets/images/gear-64x64.bmp similarity index 100% rename from assets/gear-64x64.bmp rename to rider-editor/assets/images/gear-64x64.bmp diff --git a/rider-editor/src/app/app_state.rs b/rider-editor/src/app/app_state.rs new file mode 100644 index 0000000..351d429 --- /dev/null +++ b/rider-editor/src/app/app_state.rs @@ -0,0 +1,185 @@ +use crate::app::{UpdateResult, WindowCanvas as WC}; +use crate::renderer::Renderer; +use crate::ui::*; +use rider_config::*; +use sdl2::rect::Point; +use sdl2::VideoSubsystem as VS; +use std::fs::read_to_string; +use std::sync::*; + +pub struct AppState { + menu_bar: MenuBar, + files: Vec, + config: Arc>, + file_editor: FileEditor, + open_file_modal: Option, +} + +impl AppState { + pub fn new(config: Arc>) -> Self { + Self { + menu_bar: MenuBar::new(Arc::clone(&config)), + files: vec![], + file_editor: FileEditor::new(Arc::clone(&config)), + open_file_modal: None, + config, + } + } + + #[cfg_attr(tarpaulin, skip)] + pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) { + if let Ok(buffer) = read_to_string(&file_path) { + let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); + file.prepare_ui(renderer); + match self.file_editor.open_file(file) { + Some(old) => self.files.push(old), + _ => (), + } + } else { + eprintln!("Failed to open file: {}", file_path); + }; + } + + #[cfg_attr(tarpaulin, skip)] + pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) { + match self.open_file_modal.as_mut() { + Some(modal) => modal.open_directory(dir_path, renderer), + _ => (), + }; + } + + pub fn file_editor(&self) -> &FileEditor { + &self.file_editor + } + + pub fn file_editor_mut(&mut self) -> &mut FileEditor { + &mut self.file_editor + } + + pub fn set_open_file_modal(&mut self, modal: Option) { + self.open_file_modal = modal; + } + + pub fn scroll_by(&mut self, x: i32, y: i32) { + if let Some(modal) = self.open_file_modal.as_mut() { + modal.scroll_by(x, y); + } else { + self.file_editor_mut().scroll_by(x, y); + } + } + + pub fn open_file_modal(&self) -> Option<&OpenFile> { + self.open_file_modal.as_ref() + } +} + +#[cfg_attr(tarpaulin, skip)] +impl Render for AppState { + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) { + self.file_editor + .render(canvas, renderer, &RenderContext::Nothing); + self.menu_bar + .render(canvas, renderer, &RenderContext::Nothing); + match self.open_file_modal.as_ref() { + Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing), + _ => (), + }; + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + self.menu_bar.prepare_ui(renderer); + self.file_editor.prepare_ui(renderer); + } +} + +impl Update for AppState { + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { + let res = match self.open_file_modal.as_mut() { + Some(modal) => modal.update(ticks, &UpdateContext::Nothing), + _ => UpdateResult::NoOp, + }; + if res != UpdateResult::NoOp { + return res; + } + + self.menu_bar.update(ticks, context); + self.file_editor.update(ticks, context); + UpdateResult::NoOp + } +} + +impl AppState { + #[cfg_attr(tarpaulin, skip)] + pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult { + match self.open_file_modal.as_mut() { + Some(modal) => return modal.on_left_click(point, &UpdateContext::Nothing), + _ => (), + }; + if self + .menu_bar + .is_left_click_target(point, &UpdateContext::Nothing) + { + video_subsystem.text_input().stop(); + return self.menu_bar.on_left_click(point, &UpdateContext::Nothing); + } else if !self + .file_editor + .is_left_click_target(point, &UpdateContext::Nothing) + { + return UpdateResult::NoOp; + } else { + video_subsystem.text_input().start(); + self.file_editor + .on_left_click(point, &UpdateContext::Nothing); + } + UpdateResult::NoOp + } + + pub fn is_left_click_target(&self, _point: &Point) -> bool { + true + } +} + +impl ConfigHolder for AppState { + fn config(&self) -> &ConfigAccess { + &self.config + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::support; + // use crate::ui::modal::open_file; + use std::sync::Arc; + + #[test] + fn must_return_none_for_default_file() { + let config = support::build_config(); + let state = AppState::new(Arc::clone(&config)); + let file = state.file_editor().file(); + assert_eq!(file.is_none(), true); + } + + #[test] + fn must_scroll_file_when_no_modal() { + let config = support::build_config(); + let mut state = AppState::new(Arc::clone(&config)); + let old_scroll = state.file_editor().scroll(); + state.set_open_file_modal(None); + state.scroll_by(10, 10); + assert_ne!(state.file_editor().scroll(), old_scroll); + } + + #[test] + fn must_scroll_modal_when_modal_was_set() { + let config = support::build_config(); + let mut state = AppState::new(Arc::clone(&config)); + let modal = OpenFile::new("/".to_owned(), 100, 100, Arc::clone(&config)); + let file_scroll = state.file_editor().scroll(); + let old_scroll = state.file_editor().scroll(); + state.set_open_file_modal(Some(modal)); + state.scroll_by(10, 10); + assert_eq!(state.file_editor().scroll(), file_scroll); + assert_ne!(state.open_file_modal().unwrap().scroll(), old_scroll); + } +} diff --git a/rider-editor/src/app/application.rs b/rider-editor/src/app/application.rs new file mode 100644 index 0000000..e2e34f1 --- /dev/null +++ b/rider-editor/src/app/application.rs @@ -0,0 +1,324 @@ +pub use crate::app::app_state::AppState; +pub use crate::renderer::Renderer; +use crate::ui::caret::{CaretPosition, MoveDirection}; +use crate::ui::*; +pub use rider_config::{Config, ConfigAccess, ConfigHolder}; +use sdl2::event::*; +use sdl2::hint; +use sdl2::keyboard::Keycode; +use sdl2::keyboard::Scancode; +use sdl2::mouse::*; +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use sdl2::render::Canvas; +use sdl2::rwops::RWops; +use sdl2::surface::Surface; +use sdl2::video::Window; +use sdl2::EventPump; +use sdl2::{Sdl, TimerSubsystem, VideoSubsystem}; +use std::process::Command; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; +use std::time::Duration; + +pub type WindowCanvas = Canvas; + +#[derive(PartialEq, Clone, Debug)] +pub enum UpdateResult { + NoOp, + Stop, + RefreshPositions, + MouseLeftClicked(Point), + MoveCaret(Rect, CaretPosition), + DeleteFront, + DeleteBack, + Input(String), + InsertNewLine, + MoveCaretLeft, + MoveCaretRight, + MoveCaretUp, + MoveCaretDown, + Scroll { x: i32, y: i32 }, + WindowResize { width: i32, height: i32 }, + RefreshFsTree, + OpenFile(String), + OpenDirectory(String), + OpenFileModal, +} + +#[cfg_attr(tarpaulin, skip)] +pub struct Application { + config: Arc>, + clear_color: Color, + sdl_context: Sdl, + canvas: WindowCanvas, + video_subsystem: VideoSubsystem, + tasks: Vec, +} + +#[cfg_attr(tarpaulin, skip)] +impl Application { + pub fn new() -> Self { + let generator_path = rider_config::directories::get_binary_path("rider-generator") + .unwrap_or_else(|e| panic!(e)); + Command::new(generator_path).status().unwrap(); + + let mut config = Config::new(); + config.set_theme(config.editor_config().current_theme().clone()); + let config = Arc::new(RwLock::new(config)); + let sdl_context = sdl2::init().unwrap(); + + hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1"); + hint::set("SDL_GL_MULTISAMPLESAMPLES", "8"); + hint::set("SDL_GL_ACCELERATED_VISUAL", "1"); + hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2"); + hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1"); + + let video_subsystem = sdl_context.video().unwrap(); + + let mut window: Window = { + let c = config.read().unwrap(); + video_subsystem + .window("Rider", c.width(), c.height()) + .position_centered() + .resizable() + .opengl() + .build() + .unwrap() + }; + let icon_bytes = include_bytes!("../../assets/images/gear-64x64.bmp").clone(); + let mut rw = RWops::from_bytes(&icon_bytes).unwrap(); + let mut icon = Surface::load_bmp_rw(&mut rw).unwrap(); + window.set_icon(&mut icon); + + let canvas = window.into_canvas().accelerated().build().unwrap(); + let clear_color: Color = { config.read().unwrap().theme().background().into() }; + + Self { + sdl_context, + video_subsystem, + canvas, + tasks: vec![], + clear_color, + config, + } + } + + pub fn init(&mut self) { + self.clear(); + } + + pub fn run(&mut self) { + let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap(); + let mut event_pump = self.sdl_context.event_pump().unwrap(); + let font_context = sdl2::ttf::init().unwrap(); + let texture_creator = self.canvas.texture_creator(); + let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); + let mut app_state = AppState::new(Arc::clone(&self.config)); + let mut renderer = Renderer::new(Arc::clone(&self.config), &font_context, &texture_creator); + app_state.prepare_ui(&mut renderer); + + 'running: loop { + self.handle_events(&mut event_pump); + let mut new_tasks: Vec = vec![]; + for task in self.tasks.iter() { + match task { + UpdateResult::Stop => break 'running, + UpdateResult::RefreshPositions => (), + UpdateResult::NoOp => (), + UpdateResult::MoveCaret(_, _pos) => (), + UpdateResult::MouseLeftClicked(point) => { + let res = app_state.on_left_click(&point, &mut self.video_subsystem); + match res { + UpdateResult::OpenDirectory(_) => new_tasks.push(res), + UpdateResult::OpenFile(_) => { + new_tasks.push(res); + app_state.set_open_file_modal(None); + } + _ => {} + } + } + UpdateResult::DeleteFront => { + app_state.file_editor_mut().delete_front(&mut renderer); + } + UpdateResult::DeleteBack => { + app_state.file_editor_mut().delete_back(&mut renderer); + } + UpdateResult::Input(text) => { + app_state + .file_editor_mut() + .insert_text(text.clone(), &mut renderer); + } + UpdateResult::InsertNewLine => { + app_state.file_editor_mut().insert_new_line(&mut renderer); + } + UpdateResult::MoveCaretLeft => { + app_state.file_editor_mut().move_caret(MoveDirection::Left); + } + UpdateResult::MoveCaretRight => { + app_state.file_editor_mut().move_caret(MoveDirection::Right); + } + UpdateResult::MoveCaretUp => { + app_state.file_editor_mut().move_caret(MoveDirection::Up); + } + UpdateResult::MoveCaretDown => { + app_state.file_editor_mut().move_caret(MoveDirection::Down); + } + UpdateResult::Scroll { x, y } => { + app_state.scroll_by(-x.clone(), -y.clone()); + } + UpdateResult::WindowResize { width, height } => { + let mut c = app_state.config().write().unwrap(); + let w = width.clone(); + let h = height.clone(); + if w > 0 { + c.set_width(w as u32); + } + if h > 0 { + c.set_height(h as u32); + } + } + UpdateResult::RefreshFsTree => unimplemented!(), + UpdateResult::OpenFile(file_path) => { + app_state.open_file(file_path.clone(), &mut renderer); + } + UpdateResult::OpenDirectory(dir_path) => { + app_state.open_directory(dir_path.clone(), &mut renderer); + } + UpdateResult::OpenFileModal => { + use std::env; + let pwd = env::current_dir().unwrap().to_str().unwrap().to_string(); + let mut modal = + OpenFile::new(pwd.clone(), 400, 800, Arc::clone(&self.config)); + modal.prepare_ui(&mut renderer); + modal.open_directory(pwd.clone(), &mut renderer); + app_state.set_open_file_modal(Some(modal)); + } + } + } + self.tasks = new_tasks; + + self.clear(); + + app_state.update(timer.ticks() as i32, &UpdateContext::Nothing); + app_state.render(&mut self.canvas, &mut renderer, &RenderContext::Nothing); + + self.present(); + + if !cfg!(test) { + sleep(sleep_time); + } + } + } + + pub fn open_file(&mut self, file_path: String) { + self.tasks.push(UpdateResult::OpenFile(file_path)); + } + + fn present(&mut self) { + self.canvas.present(); + } + + fn clear(&mut self) { + self.canvas.set_draw_color(self.clear_color.clone()); + self.canvas.clear(); + } + + fn handle_events(&mut self, event_pump: &mut EventPump) { + let left_control_pressed = event_pump + .keyboard_state() + .is_scancode_pressed(Scancode::LCtrl); + let shift_pressed = event_pump + .keyboard_state() + .is_scancode_pressed(Scancode::LShift) + || event_pump + .keyboard_state() + .is_scancode_pressed(Scancode::RShift); + + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } => self.tasks.push(UpdateResult::Stop), + Event::MouseButtonUp { + mouse_btn, x, y, .. + } => match mouse_btn { + MouseButton::Left => self + .tasks + .push(UpdateResult::MouseLeftClicked(Point::new(x, y))), + _ => (), + }, + Event::KeyDown { keycode, .. } => { + let keycode = if keycode.is_some() { + keycode.unwrap() + } else { + continue; + }; + + match keycode { + Keycode::Backspace => { + self.tasks.push(UpdateResult::DeleteFront); + } + Keycode::Delete => { + self.tasks.push(UpdateResult::DeleteBack); + } + Keycode::KpEnter | Keycode::Return => { + self.tasks.push(UpdateResult::InsertNewLine); + } + Keycode::Left => { + self.tasks.push(UpdateResult::MoveCaretLeft); + } + Keycode::Right => { + self.tasks.push(UpdateResult::MoveCaretRight); + } + Keycode::Up => { + self.tasks.push(UpdateResult::MoveCaretUp); + } + Keycode::Down => { + self.tasks.push(UpdateResult::MoveCaretDown); + } + Keycode::O => { + if left_control_pressed && !shift_pressed { + self.tasks.push(UpdateResult::OpenFileModal); + } + } + _ => {} + }; + } + Event::TextInput { text, .. } => { + self.tasks.push(UpdateResult::Input(text)); + } + Event::MouseWheel { + direction, x, y, .. + } => { + match direction { + MouseWheelDirection::Normal => { + self.tasks.push(UpdateResult::Scroll { x, y }); + } + MouseWheelDirection::Flipped => { + self.tasks.push(UpdateResult::Scroll { x, y: -y }); + } + _ => { + // ignore + } + }; + } + Event::Window { + win_event: WindowEvent::Resized(w, h), + .. + } => { + self.tasks.push(UpdateResult::WindowResize { + width: w, + height: h, + }); + } + _ => {} + } + } + } +} + +#[cfg_attr(tarpaulin, skip)] +impl ConfigHolder for Application { + fn config(&self) -> &ConfigAccess { + &self.config + } +} diff --git a/rider-editor/src/app/caret_manager.rs b/rider-editor/src/app/caret_manager.rs new file mode 100644 index 0000000..090cbc2 --- /dev/null +++ b/rider-editor/src/app/caret_manager.rs @@ -0,0 +1,67 @@ +use crate::ui::*; +use sdl2::rect::Point; + +pub fn move_caret_right(file_editor: &mut FileEditor) { + let file: &EditorFile = match file_editor.file() { + None => return, + Some(f) => f, + }; + let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) { + Some(text_character) => text_character, + None => return, // EOF + }; + let pos = file_editor.caret().position(); + let d = c.dest().clone(); + let p = pos.moved(1, 0, 0); + file_editor + .caret_mut() + .move_caret(p, Point::new(d.x(), d.y())); +} + +pub fn move_caret_left(file_editor: &mut FileEditor) { + let file: &EditorFile = match file_editor.file() { + None => return, + Some(f) => f, + }; + if file_editor.caret().text_position() == 0 { + return; + } + let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() - 1) { + Some(text_character) => text_character, + None => return, // EOF + }; + let pos = file_editor.caret().position(); + let d = c.dest().clone(); + let p = pos.moved(-1, 0, 0); + file_editor + .caret_mut() + .move_caret(p, Point::new(d.x(), d.y())); +} + +#[cfg(test)] +mod test_move_right { + use super::*; + use crate::tests::support; + + #[test] + fn must_do_nothing() { + let config = support::build_config(); + let mut editor = FileEditor::new(config); + + assert_eq!(move_caret_right(&mut editor), ()); + } +} + +#[cfg(test)] +mod test_move_left { + use super::*; + use crate::tests::support; + + #[test] + fn must_do_nothing() { + let config = support::build_config(); + let mut editor = FileEditor::new(config); + + assert_eq!(move_caret_left(&mut editor), ()); + } +} diff --git a/src/app/file_content_manager.rs b/rider-editor/src/app/file_content_manager.rs similarity index 70% rename from src/app/file_content_manager.rs rename to rider-editor/src/app/file_content_manager.rs index 3f5cac7..acab0ff 100644 --- a/src/app/file_content_manager.rs +++ b/rider-editor/src/app/file_content_manager.rs @@ -1,15 +1,16 @@ use crate::app::*; use crate::renderer::Renderer; use crate::ui::*; -use sdl2::rect::*; +use sdl2::rect::{Point, Rect}; use std::sync::*; -fn current_file_path(file_editor: &mut FileEditor) -> String { +pub fn current_file_path(file_editor: &mut FileEditor) -> String { file_editor .file() .map_or_else(|| String::new(), |f| f.path()) } +#[cfg_attr(tarpaulin, skip)] pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { let mut buffer: String = if let Some(file) = file_editor.file() { file @@ -35,7 +36,7 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { .file() .and_then(|f| f.get_character_at(file_editor.caret().text_position())) .and_then(|character| { - let dest: &Rect = character.dest(); + let dest: Rect = character.dest(); Some((position, Point::new(dest.x(), dest.y()))) }); match move_to { @@ -51,6 +52,7 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { file_editor.replace_current_file(new_file); } +#[cfg_attr(tarpaulin, skip)] pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { let file: &EditorFile = if let Some(file) = file_editor.file() { file @@ -68,6 +70,7 @@ pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { file_editor.replace_current_file(new_file); } +#[cfg_attr(tarpaulin, skip)] pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) { let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer()); if buffer.is_empty() { @@ -81,15 +84,19 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re Some(c) => c, _ => return, }; - let mut pos = Point::new(current.dest().x(), current.dest().y()); + let mut pos = if current.is_new_line() { + current.dest().top_left() + + Point::new(0, renderer.load_character_size('\n').height() as i32) + } else { + current.dest().top_left() + }; let mut position: CaretPosition = file_editor.caret().position().clone(); for character in text.chars() { buffer.insert(position.text_position(), character); - if let Some(rect) = get_text_character_rect(character, renderer) { - pos = pos + Point::new(rect.width() as i32, 0); - position = position.moved(1, 0, 0); - file_editor.caret_mut().move_caret(position, pos.clone()); - } + let rect = renderer.load_character_size(character); + pos = pos + Point::new(rect.width() as i32, 0); + position = position.moved(1, 0, 0); + file_editor.caret_mut().move_caret(position, pos.clone()); } let mut new_file = EditorFile::new( @@ -101,6 +108,7 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re file_editor.replace_current_file(new_file); } +#[cfg_attr(tarpaulin, skip)] pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { let mut buffer: String = if let Some(file) = file_editor.file() { file @@ -115,17 +123,14 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { Some(c) => c, _ => return, }; + let mut pos = Point::new(current.dest().x(), current.dest().y()); let mut position: CaretPosition = file_editor.caret().position().clone(); buffer.insert(position.text_position(), '\n'); - if let Some(rect) = get_text_character_rect('\n', renderer) { - pos = Point::new( - file_editor.config().read().unwrap().editor_left_margin(), - pos.y() + rect.height() as i32, - ); - position = position.moved(0, 1, 0); - file_editor.caret_mut().move_caret(position, pos.clone()); - } + let rect = renderer.load_character_size('\n'); + pos = Point::new(0, pos.y() + rect.height() as i32); + position = position.moved(0, 1, 0); + file_editor.caret_mut().move_caret(position, pos.clone()); let mut new_file = EditorFile::new( current_file_path(file_editor), @@ -135,3 +140,31 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { new_file.prepare_ui(renderer); file_editor.replace_current_file(new_file); } + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::support; + + #[test] + fn must_return_empty_string_when_no_file() { + let config = support::build_config(); + let mut editor = FileEditor::new(config); + let result = current_file_path(&mut editor); + assert_eq!(result, String::new()); + } + + #[test] + fn must_return_path_string_when_file_was_set() { + let config = support::build_config(); + let mut editor = FileEditor::new(Arc::clone(&config)); + let file = EditorFile::new( + "/foo/bar".to_owned(), + "hello world".to_owned(), + Arc::clone(&config), + ); + editor.open_file(file); + let result = current_file_path(&mut editor); + assert_eq!(result, "/foo/bar".to_owned()); + } +} diff --git a/src/app/mod.rs b/rider-editor/src/app/mod.rs similarity index 100% rename from src/app/mod.rs rename to rider-editor/src/app/mod.rs diff --git a/rider-editor/src/main.rs b/rider-editor/src/main.rs new file mode 100644 index 0000000..f864617 --- /dev/null +++ b/rider-editor/src/main.rs @@ -0,0 +1,57 @@ +extern crate dirs; +#[macro_use] +extern crate log; +extern crate rand; +extern crate rider_config; +extern crate rider_lexers; +extern crate rider_themes; +extern crate sdl2; +extern crate serde; +extern crate serde_derive; +extern crate serde_json; +extern crate simplelog; + +use crate::app::Application; +use rider_config::directories::Directories; +use simplelog::*; +use std::fs::File; + +pub mod app; +pub mod renderer; +#[cfg(test)] +pub mod tests; +pub mod ui; + +#[cfg_attr(tarpaulin, skip)] +fn init_logger(directories: &Directories) { + use simplelog::SharedLogger; + + let mut log_file_path = directories.log_dir.clone(); + log_file_path.push("rider.log"); + + let mut outputs: Vec> = vec![WriteLogger::new( + LevelFilter::Info, + Config::default(), + File::create(log_file_path).unwrap(), + )]; + let terminal_level = if cfg!(release) { + LevelFilter::Trace + } else { + LevelFilter::Debug + }; + if let Some(term) = TermLogger::new(terminal_level, Config::default()) { + outputs.push(term); + } + + CombinedLogger::init(outputs).unwrap(); +} + +#[cfg_attr(tarpaulin, skip)] +fn main() { + let directories = Directories::new(None, None); + let mut app = Application::new(); + app.init(); + init_logger(&directories); + app.open_file("./test_files/test.rs".to_string()); + app.run(); +} diff --git a/src/renderer/managers.rs b/rider-editor/src/renderer/managers.rs similarity index 75% rename from src/renderer/managers.rs rename to rider-editor/src/renderer/managers.rs index 673643a..ad6afdd 100644 --- a/src/renderer/managers.rs +++ b/rider-editor/src/renderer/managers.rs @@ -2,7 +2,8 @@ use sdl2::image::LoadTexture; use sdl2::pixels::Color; use sdl2::render::{Texture, TextureCreator}; use sdl2::ttf::{Font, Sdl2TtfContext}; -use sdl2::video::WindowContext as WinCtxt; +//use sdl2::video::WindowContext as WinCtxt; +use rider_config::editor_config::EditorConfig; use std::borrow::Borrow; use std::collections::HashMap; #[allow(unused_imports)] @@ -10,22 +11,41 @@ use std::env; use std::hash::Hash; use std::rc::Rc; +#[cfg_attr(tarpaulin, skip)] //noinspection RsWrongLifetimeParametersNumber pub type RcTex<'l> = Rc>; +#[cfg_attr(tarpaulin, skip)] pub type RcFont<'l> = Rc>; +#[cfg_attr(tarpaulin, skip)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TextCharacterDetails { + pub c: char, + pub font_path: String, + pub font_size: u16, +} + +#[cfg_attr(tarpaulin, skip)] pub trait ResourceLoader<'l, R> { type Args: ?Sized; fn load(&'l self, data: &Self::Args) -> Result; } +#[cfg_attr(tarpaulin, skip)] #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct FontDetails { pub path: String, pub size: u16, } +impl From<&EditorConfig> for FontDetails { + fn from(config: &EditorConfig) -> Self { + FontDetails::new(config.font_path().as_str(), config.character_size().clone()) + } +} + +#[cfg_attr(tarpaulin, skip)] #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct TextDetails { pub text: String, @@ -33,6 +53,7 @@ pub struct TextDetails { pub font: FontDetails, } +#[cfg_attr(tarpaulin, skip)] impl TextDetails { pub fn get_cache_key(&self) -> String { format!( @@ -43,6 +64,7 @@ impl TextDetails { } } +#[cfg_attr(tarpaulin, skip)] impl<'a> From<&'a TextDetails> for TextDetails { fn from(details: &'a Self) -> Self { Self { @@ -53,6 +75,7 @@ impl<'a> From<&'a TextDetails> for TextDetails { } } +#[cfg_attr(tarpaulin, skip)] impl FontDetails { pub fn new(path: &str, size: u16) -> FontDetails { Self { @@ -62,6 +85,7 @@ impl FontDetails { } } +#[cfg_attr(tarpaulin, skip)] impl<'a> From<&'a FontDetails> for FontDetails { fn from(details: &'a FontDetails) -> Self { Self { @@ -71,16 +95,21 @@ impl<'a> From<&'a FontDetails> for FontDetails { } } +#[cfg_attr(tarpaulin, skip)] //noinspection RsWrongLifetimeParametersNumber -pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator>; +pub type TextureManager<'l> = + ResourceManager<'l, String, Texture<'l>, TextureCreator>; +#[cfg_attr(tarpaulin, skip)] pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; +#[cfg_attr(tarpaulin, skip)] pub trait ManagersHolder<'l> { fn font_manager(&mut self) -> &mut FontManager<'l>; - fn texture_manager(&mut self) -> &mut TextureManager<'l, WinCtxt>; + fn texture_manager(&mut self) -> &mut TextureManager<'l>; } +#[cfg_attr(tarpaulin, skip)] #[derive(Clone)] pub struct ResourceManager<'l, K, R, L> where @@ -91,6 +120,7 @@ where cache: HashMap>, } +#[cfg_attr(tarpaulin, skip)] impl<'l, K, R, L> ResourceManager<'l, K, R, L> where K: Hash + Eq, @@ -124,6 +154,7 @@ where } } +#[cfg_attr(tarpaulin, skip)] //noinspection RsWrongLifetimeParametersNumber impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { type Args = str; @@ -134,6 +165,7 @@ impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { } } +#[cfg_attr(tarpaulin, skip)] impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { type Args = FontDetails; @@ -143,6 +175,7 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { } } +#[cfg_attr(tarpaulin, skip)] pub trait TextTextureManager<'l> { //noinspection RsWrongLifetimeParametersNumber fn load_text( @@ -152,7 +185,8 @@ pub trait TextTextureManager<'l> { ) -> Result>, String>; } -impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> { +#[cfg_attr(tarpaulin, skip)] +impl<'l> TextTextureManager<'l> for TextureManager<'l> { //noinspection RsWrongLifetimeParametersNumber fn load_text( &mut self, @@ -169,9 +203,9 @@ impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> { let texture = self.loader.create_texture_from_surface(&surface).unwrap(); let resource = Rc::new(texture); self.cache.insert(key, resource.clone()); - for c in details.text.chars() { - info!("texture for '{:?}' created", c); - } + // for c in details.text.chars() { + // info!("texture for '{:?}' created", c); + // } Ok(resource) }, Ok, diff --git a/src/renderer/mod.rs b/rider-editor/src/renderer/mod.rs similarity index 100% rename from src/renderer/mod.rs rename to rider-editor/src/renderer/mod.rs diff --git a/rider-editor/src/renderer/renderer.rs b/rider-editor/src/renderer/renderer.rs new file mode 100644 index 0000000..cb1aa7b --- /dev/null +++ b/rider-editor/src/renderer/renderer.rs @@ -0,0 +1,83 @@ +use crate::renderer::managers::*; +use crate::ui::get_text_character_rect; +use crate::ui::text_character::CharacterSizeManager; +use rider_config::{ConfigAccess, ConfigHolder}; +use sdl2::rect::Rect; +use sdl2::render::TextureCreator; +use sdl2::ttf::Sdl2TtfContext; +use std::collections::HashMap; + +#[cfg_attr(tarpaulin, skip)] +pub struct Renderer<'l> { + config: ConfigAccess, + font_manager: FontManager<'l>, + texture_manager: TextureManager<'l>, + character_sizes: HashMap, +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> Renderer<'l> { + pub fn new( + config: ConfigAccess, + font_context: &'l Sdl2TtfContext, + texture_creator: &'l TextureCreator, + ) -> Self { + let texture_manager = TextureManager::new(&texture_creator); + let font_manager = FontManager::new(&font_context); + Self { + config, + font_manager, + texture_manager, + character_sizes: HashMap::new(), + } + } + + pub fn character_sizes_mut(&mut self) -> &mut HashMap { + &mut self.character_sizes + } +} + +impl<'l> CharacterSizeManager for Renderer<'l> { + fn load_character_size(&mut self, c: char) -> Rect { + let (font_path, font_size) = { + let config = self.config().read().unwrap(); + ( + config.editor_config().font_path().clone(), + config.editor_config().character_size().clone(), + ) + }; + let details = TextCharacterDetails { + c: c.clone(), + font_path, + font_size, + }; + self.character_sizes + .get(&details) + .cloned() + .or_else(|| { + let size = get_text_character_rect(c, self).unwrap(); + self.character_sizes.insert(details.clone(), size.clone()); + Some(size) + }) + .unwrap() + .clone() + } +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> ManagersHolder<'l> for Renderer<'l> { + fn font_manager(&mut self) -> &mut FontManager<'l> { + &mut self.font_manager + } + + fn texture_manager(&mut self) -> &mut TextureManager<'l> { + &mut self.texture_manager + } +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> ConfigHolder for Renderer<'l> { + fn config(&self) -> &ConfigAccess { + &self.config + } +} diff --git a/rider-editor/src/tests.rs b/rider-editor/src/tests.rs new file mode 100644 index 0000000..345bdcc --- /dev/null +++ b/rider-editor/src/tests.rs @@ -0,0 +1,11 @@ +#[cfg(test)] +pub mod support { + use rider_config::Config; + use std::sync::*; + + pub fn build_config() -> Arc> { + let mut config = Config::new(); + config.set_theme(config.editor_config().current_theme().clone()); + Arc::new(RwLock::new(config)) + } +} diff --git a/src/ui/caret/caret.rs b/rider-editor/src/ui/caret/caret.rs similarity index 93% rename from src/ui/caret/caret.rs rename to rider-editor/src/ui/caret/caret.rs index fe627b3..7efb947 100644 --- a/src/ui/caret/caret.rs +++ b/rider-editor/src/ui/caret/caret.rs @@ -1,13 +1,9 @@ use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; use crate::renderer::*; -use crate::ui::text_character::TextCharacter; use crate::ui::*; -use sdl2::pixels::Color; +use rider_config::ConfigAccess; use sdl2::rect::{Point, Rect}; -use sdl2::render::Texture; use std::ops::Deref; -use std::sync::*; #[derive(Clone, Debug, PartialEq)] pub struct Caret { @@ -70,13 +66,13 @@ impl Deref for Caret { } } +#[cfg_attr(tarpaulin, skip)] impl Render for Caret { fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { use std::borrow::*; - use std::option::*; let dest = match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()), + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), _ => self.dest().clone(), }; let start = Point::new(dest.x(), dest.y()); @@ -121,11 +117,12 @@ impl ClickHandler for Caret { } fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let dest = self.dest(); is_in_rect( point, &match context { - &UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()), - _ => self.dest().clone(), + &UpdateContext::ParentPosition(p) => move_render_point(p, &dest), + _ => dest, }, ) } @@ -136,18 +133,16 @@ impl RenderBox for Caret { self.dest().top_left() } - fn dest(&self) -> &Rect { - &self.dest + fn dest(&self) -> Rect { + self.dest.clone() } } #[cfg(test)] mod test_own_methods { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::{Point, Rect}; use std::sync::*; #[test] @@ -204,11 +199,9 @@ mod test_own_methods { #[cfg(test)] mod test_deref { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Point; use std::sync::*; #[test] @@ -244,11 +237,9 @@ mod test_deref { #[cfg(test)] mod test_render_box { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Point; use std::sync::*; #[test] @@ -264,11 +255,9 @@ mod test_render_box { #[cfg(test)] mod test_click_handler { use crate::app::*; - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Point; use std::sync::*; #[test] diff --git a/src/ui/caret/caret_color.rs b/rider-editor/src/ui/caret/caret_color.rs similarity index 97% rename from src/ui/caret/caret_color.rs rename to rider-editor/src/ui/caret/caret_color.rs index 98ec5d8..0e1b5d4 100644 --- a/src/ui/caret/caret_color.rs +++ b/rider-editor/src/ui/caret/caret_color.rs @@ -23,7 +23,6 @@ impl CaretColor { #[cfg(test)] mod test_getters { use super::*; - use sdl2::pixels::*; #[test] fn assert_bright() { diff --git a/src/ui/caret/caret_position.rs b/rider-editor/src/ui/caret/caret_position.rs similarity index 100% rename from src/ui/caret/caret_position.rs rename to rider-editor/src/ui/caret/caret_position.rs diff --git a/src/ui/caret/mod.rs b/rider-editor/src/ui/caret/mod.rs similarity index 100% rename from src/ui/caret/mod.rs rename to rider-editor/src/ui/caret/mod.rs diff --git a/src/ui/file/editor_file.rs b/rider-editor/src/ui/file/editor_file.rs similarity index 96% rename from src/ui/file/editor_file.rs rename to rider-editor/src/ui/file/editor_file.rs index 51f9d2e..8741d8a 100644 --- a/src/ui/file/editor_file.rs +++ b/rider-editor/src/ui/file/editor_file.rs @@ -1,13 +1,12 @@ use sdl2::rect::{Point, Rect}; -use std::rc::Rc; use std::sync::*; use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::Config; use crate::renderer::Renderer; use crate::ui::file::editor_file_section::EditorFileSection; use crate::ui::text_character::TextCharacter; use crate::ui::*; +use rider_config::Config; #[derive(Clone, Debug)] pub struct EditorFile { @@ -114,6 +113,7 @@ impl TextCollection for EditorFile { } } +#[cfg_attr(tarpaulin, skip)] impl Render for EditorFile { fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { for section in self.sections.iter() { @@ -196,21 +196,16 @@ impl RenderBox for EditorFile { self.dest.top_left() } - fn dest(&self) -> &Rect { - &self.dest + fn dest(&self) -> Rect { + self.dest.clone() } } #[cfg(test)] mod test_render_box { - use crate::app::*; use crate::tests::support; use crate::ui::*; - use sdl2::rect::*; - use sdl2::*; - use std::borrow::*; - use std::rc::*; - use std::sync::*; + use sdl2::rect::{Point, Rect}; #[test] fn assert_dest() { diff --git a/src/ui/file/editor_file_section.rs b/rider-editor/src/ui/file/editor_file_section.rs similarity index 96% rename from src/ui/file/editor_file_section.rs rename to rider-editor/src/ui/file/editor_file_section.rs index a2e24a4..c972616 100644 --- a/src/ui/file/editor_file_section.rs +++ b/rider-editor/src/ui/file/editor_file_section.rs @@ -1,15 +1,14 @@ use sdl2::rect::{Point, Rect}; -use std::cell::Cell; -use std::rc::Rc; use std::sync::*; use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::Config; -use crate::lexer::Language; use crate::renderer::Renderer; use crate::ui::file::editor_file_token::EditorFileToken; use crate::ui::text_character::TextCharacter; use crate::ui::*; +use rider_config::Config; +use rider_lexers; +use rider_lexers::Language; #[derive(Clone, Debug)] pub struct EditorFileSection { @@ -20,8 +19,6 @@ pub struct EditorFileSection { impl EditorFileSection { pub fn new(buffer: String, ext: String, config: Arc>) -> Self { - use crate::lexer; - let language = config .read() .unwrap() @@ -29,7 +26,7 @@ impl EditorFileSection { .get(ext.as_str()) .unwrap_or(&Language::PlainText) .clone(); - let lexer_tokens = lexer::parse(buffer.clone(), &language); + let lexer_tokens = rider_lexers::parse(buffer.clone(), language); let mut tokens: Vec = vec![]; let mut iterator = lexer_tokens.iter().peekable(); @@ -128,6 +125,7 @@ impl TextCollection for EditorFileSection { } } +#[cfg_attr(tarpaulin, skip)] impl Render for EditorFileSection { fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { for token in self.tokens.iter() { diff --git a/src/ui/file/editor_file_token.rs b/rider-editor/src/ui/file/editor_file_token.rs similarity index 92% rename from src/ui/file/editor_file_token.rs rename to rider-editor/src/ui/file/editor_file_token.rs index 3625e4d..b47bdf9 100644 --- a/src/ui/file/editor_file_token.rs +++ b/rider-editor/src/ui/file/editor_file_token.rs @@ -1,38 +1,17 @@ use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; -use crate::lexer::TokenType; -use crate::renderer::managers::{FontDetails, TextDetails}; use crate::renderer::*; use crate::ui::*; +use rider_config::Config; +use rider_lexers::TokenType; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -use sdl2::render::Texture; -use sdl2::ttf::Font; -use std::rc::Rc; use std::sync::*; -impl TokenType { - pub fn to_color(&self, config: &Arc>) -> Color { - let config = config.read().unwrap(); - let ch = config.theme().code_highlighting(); - match self { - &TokenType::Whitespace { .. } => ch.whitespace().color().into(), - &TokenType::Keyword { .. } => ch.keyword().color().into(), - &TokenType::String { .. } => ch.string().color().into(), - &TokenType::Identifier { .. } => ch.identifier().color().into(), - &TokenType::Literal { .. } => ch.literal().color().into(), - &TokenType::Comment { .. } => ch.comment().color().into(), - &TokenType::Operator { .. } => ch.operator().color().into(), - &TokenType::Separator { .. } => ch.separator().color().into(), - } - } -} - #[derive(Clone, Debug)] pub struct EditorFileToken { last_in_line: bool, characters: Vec, - token_type: Rc, + token_type: TokenType, config: Arc>, } @@ -41,7 +20,7 @@ impl EditorFileToken { Self { last_in_line, characters: vec![], - token_type: Rc::new(token_type.clone()), + token_type: token_type.clone(), config, } } @@ -59,6 +38,21 @@ impl EditorFileToken { text_character.update_position(current); } } + + fn token_to_color(&self, config: &Arc>) -> Color { + let config = config.read().unwrap(); + let ch = config.theme().code_highlighting(); + match &self.token_type { + &TokenType::Whitespace { .. } => ch.whitespace().color().into(), + &TokenType::Keyword { .. } => ch.keyword().color().into(), + &TokenType::String { .. } => ch.string().color().into(), + &TokenType::Identifier { .. } => ch.identifier().color().into(), + &TokenType::Literal { .. } => ch.literal().color().into(), + &TokenType::Comment { .. } => ch.comment().color().into(), + &TokenType::Operator { .. } => ch.operator().color().into(), + &TokenType::Separator { .. } => ch.separator().color().into(), + } + } } impl TextWidget for EditorFileToken { @@ -129,6 +123,7 @@ impl TextCollection for EditorFileToken { } } +#[cfg_attr(tarpaulin, skip)] impl Render for EditorFileToken { /** * Must first create targets so even if new line appear renderer will know @@ -147,7 +142,7 @@ impl Render for EditorFileToken { if !self.characters.is_empty() { return; } - let color: Color = self.token_type.to_color(renderer.config()); + let color: Color = self.token_to_color(&renderer.config()); let chars: Vec = self.token_type.text().chars().collect(); for (index, c) in chars.iter().enumerate() { let last_in_line = self.last_in_line && index + 1 == chars.len(); diff --git a/src/ui/file/mod.rs b/rider-editor/src/ui/file/mod.rs similarity index 100% rename from src/ui/file/mod.rs rename to rider-editor/src/ui/file/mod.rs diff --git a/src/ui/file_editor/file_editor.rs b/rider-editor/src/ui/file_editor/file_editor.rs similarity index 95% rename from src/ui/file_editor/file_editor.rs rename to rider-editor/src/ui/file_editor/file_editor.rs index 3d5f1e0..683aac1 100644 --- a/src/ui/file_editor/file_editor.rs +++ b/rider-editor/src/ui/file_editor/file_editor.rs @@ -1,15 +1,12 @@ -use sdl2::pixels::*; -use sdl2::rect::*; -use std::borrow::*; -use std::mem; -use std::sync::*; - use crate::app::*; use crate::app::{UpdateResult as UR, WindowCanvas as WS}; use crate::ui::scroll_bar::horizontal_scroll_bar::*; use crate::ui::scroll_bar::vertical_scroll_bar::*; use crate::ui::scroll_bar::Scrollable; use crate::ui::*; +use sdl2::rect::{Point, Rect}; +use std::mem; +use std::sync::*; pub struct FileEditor { dest: Rect, @@ -84,7 +81,7 @@ impl FileEditor { } impl ScrollableView for FileEditor { - fn scroll_to(&mut self, x: i32, y: i32) { + fn scroll_by(&mut self, x: i32, y: i32) { let read_config = self.config.read().unwrap(); let value_x = read_config.scroll().speed() * x; @@ -133,6 +130,8 @@ impl FileAccess for FileEditor { if let Some(f) = self.file.as_ref() { self.full_rect = f.full_rect(); } + self.vertical_scroll_bar.set_location(0); + self.horizontal_scroll_bar.set_location(0); file } @@ -162,7 +161,7 @@ impl CaretAccess for FileEditor { fn move_caret(&mut self, dir: MoveDirection) { match dir { - MoveDirection::Left => {} + MoveDirection::Left => caret_manager::move_caret_left(self), MoveDirection::Right => caret_manager::move_caret_right(self), MoveDirection::Up => {} MoveDirection::Down => {} @@ -197,6 +196,7 @@ impl CaretAccess for FileEditor { } } +#[cfg_attr(tarpaulin, skip)] impl Render for FileEditor { fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) { canvas.set_clip_rect(self.dest.clone()); @@ -215,12 +215,10 @@ impl Render for FileEditor { ); self.vertical_scroll_bar.render( canvas, - renderer, &RenderContext::RelativePosition(self.dest.top_left()), ); self.horizontal_scroll_bar.render( canvas, - renderer, &RenderContext::RelativePosition(self.dest.top_left()), ); } @@ -301,8 +299,8 @@ impl RenderBox for FileEditor { self.dest.top_left() + self.scroll() } - fn dest(&self) -> &Rect { - &self.dest + fn dest(&self) -> Rect { + self.dest.clone() } } @@ -314,12 +312,8 @@ impl ConfigHolder for FileEditor { #[cfg(test)] mod tests { - use crate::app::*; use crate::ui::*; - use sdl2::rect::*; - use sdl2::*; - use std::borrow::*; - use std::rc::*; + use rider_config::Config; use std::sync::*; #[test] @@ -344,10 +338,6 @@ mod test_config_holder { use crate::app::*; use crate::tests::support; use crate::ui::*; - use sdl2::rect::*; - use sdl2::*; - use std::borrow::*; - use std::rc::*; use std::sync::*; #[test] @@ -369,14 +359,9 @@ mod test_config_holder { #[cfg(test)] mod test_render_box { - use crate::app::*; use crate::tests::support; use crate::ui::*; - use sdl2::rect::*; - use sdl2::*; - use std::borrow::*; - use std::rc::*; - use std::sync::*; + use sdl2::rect::{Point, Rect}; impl FileEditor { pub fn set_full_rect(&mut self, r: Rect) { @@ -421,7 +406,7 @@ mod test_render_box { widget.set_dest(Rect::new(x.clone(), y.clone(), 999, 999)); widget.set_full_rect(Rect::new(0, 0, 99999, 99999)); widget.update(1, &UpdateContext::Nothing); - widget.scroll_to(30, 40); + widget.scroll_by(30, 40); let result = widget.render_start_point().clone(); let expected = Point::new(x - (ss * 30), y - (ss * 40)); assert_eq!(result, expected); diff --git a/src/ui/file_editor/mod.rs b/rider-editor/src/ui/file_editor/mod.rs similarity index 91% rename from src/ui/file_editor/mod.rs rename to rider-editor/src/ui/file_editor/mod.rs index 5ed267c..1c3e406 100644 --- a/src/ui/file_editor/mod.rs +++ b/rider-editor/src/ui/file_editor/mod.rs @@ -1,10 +1,9 @@ +pub use crate::ui::file_editor::file_editor::*; use crate::ui::*; -use sdl2::rect::*; +use sdl2::rect::Point; pub mod file_editor; -pub use crate::ui::file_editor::file_editor::*; - pub trait FileAccess { fn has_file(&self) -> bool; @@ -30,7 +29,7 @@ pub trait CaretAccess { } pub trait ScrollableView { - fn scroll_to(&mut self, x: i32, y: i32); + fn scroll_by(&mut self, x: i32, y: i32); fn scroll(&self) -> Point; } diff --git a/rider-editor/src/ui/filesystem/directory.rs b/rider-editor/src/ui/filesystem/directory.rs new file mode 100644 index 0000000..89f0638 --- /dev/null +++ b/rider-editor/src/ui/filesystem/directory.rs @@ -0,0 +1,432 @@ +use crate::app::*; +use crate::renderer::*; +use crate::ui::*; +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use std::fs; +use std::path; +use std::sync::Arc; + +const CHILD_MARGIN: i32 = 4; +const DEFAULT_ICON_SIZE: u32 = 16; + +pub struct DirectoryView { + opened: bool, + expanded: bool, + name_width: u32, + icon_width: u32, + icon_height: u32, + height: u32, + path: String, + files: Vec, + directories: Vec, + pos: Point, + source: Rect, + config: ConfigAccess, +} + +impl DirectoryView { + pub fn new(path: String, config: ConfigAccess) -> Self { + Self { + opened: false, + expanded: false, + name_width: 0, + icon_width: DEFAULT_ICON_SIZE, + icon_height: DEFAULT_ICON_SIZE, + height: 0, + path, + files: vec![], + directories: vec![], + pos: Point::new(0, 0), + source: Rect::new(0, 0, 64, 64), + config, + } + } + + pub fn path(&self) -> String { + self.path.clone() + } + + pub fn dest(&self) -> Rect { + match self.expanded { + true => Rect::new( + self.pos.x(), + self.pos.y(), + self.icon_width + self.name_width + NAME_MARGIN as u32, + self.height, + ), + false => Rect::new( + self.pos.x(), + self.pos.y(), + self.icon_width + self.name_width + NAME_MARGIN as u32, + self.icon_height, + ), + } + } + + pub fn source(&self) -> &Rect { + &self.source + } + + pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) -> bool { + match dir_path { + _ if dir_path == self.path => { + if !self.opened { + self.opened = true; + self.expanded = true; + self.read_directory(renderer); + } else { + self.expanded = !self.expanded; + } + self.calculate_size(renderer); + true + } + _ if dir_path.contains((self.path.clone() + "/").as_str()) => { + if !self.opened { + self.opened = true; + self.expanded = true; + self.read_directory(renderer); + } + for dir in self.directories.iter_mut() { + if dir.open_directory(dir_path.clone(), renderer) { + break; + } + } + self.calculate_size(renderer); + true + } + _ => false, + } + } + + pub fn refresh(&mut self) { + unimplemented!() + } + + pub fn name(&self) -> String { + path::Path::new(&self.path) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_owned() + } + + pub fn name_width(&self) -> u32 { + self.name_width + } + + pub fn icon_width(&self) -> u32 { + self.icon_width + } + + pub fn height(&self) -> u32 { + match self.expanded { + true => self.height, + false => self.icon_height, + } + } + + fn read_directory(&mut self, renderer: &mut Renderer) { + let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) { + Ok(d) => d, + _ => return, + }; + for e in entries { + let entry = match e { + Ok(entry) => entry, + _ => continue, + }; + let meta = match entry.metadata() { + Ok(meta) => meta, + _ => continue, + }; + if meta.is_dir() { + let path = match entry.path().to_str() { + Some(p) => p.to_string(), + _ => continue, + }; + let mut directory_view = DirectoryView::new(path, Arc::clone(&self.config)); + directory_view.prepare_ui(renderer); + self.directories.push(directory_view); + } else if meta.is_file() { + let file_name = match entry.file_name().to_str() { + Some(p) => p.to_string(), + _ => continue, + }; + let path = match entry.path().to_str() { + Some(p) => p.to_string(), + _ => continue, + }; + let mut file_entry = FileEntry::new(file_name, path, Arc::clone(&self.config)); + file_entry.prepare_ui(renderer); + self.files.push(file_entry); + } + } + self.files.sort_by(|a, b| a.name().cmp(&b.name())); + self.directories.sort_by(|a, b| a.name().cmp(&b.name())); + } + + fn render_icon(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + where + T: RenderImage, + { + let dir_texture_path = { + let c = self.config.read().unwrap(); + let mut themes_dir = c.directories().themes_dir.clone(); + let path = c.theme().images().directory_icon(); + themes_dir.push(path); + themes_dir.to_str().unwrap().to_owned() + }; + let texture = renderer + .texture_manager() + .load(dir_texture_path.as_str()) + .unwrap_or_else(|_| panic!("Failed to load directory entry texture")); + + canvas + .render_image( + texture, + self.source.clone(), + Rect::new(dest.x(), dest.y(), self.icon_width, self.icon_height), + ) + .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); + } + + fn render_name(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + where + T: RenderImage, + { + let mut d = dest.clone(); + d.set_x(dest.x() + NAME_MARGIN); + let font_details = build_font_details(self); + let font = renderer.font_manager().load(&font_details).unwrap(); + let name = self.name(); + let config = self.config.read().unwrap(); + let text_color = config.theme().code_highlighting().title.color(); + + for c in name.chars() { + let size = renderer.load_character_size(c.clone()); + let mut text_details = TextDetails { + color: Color::RGBA(text_color.r, text_color.g, text_color.b, text_color.a), + text: c.to_string(), + font: font_details.clone(), + }; + let text_texture = renderer + .texture_manager() + .load_text(&mut text_details, &font) + .unwrap(); + d.set_width(size.width()); + d.set_height(size.height()); + + canvas + .render_image(text_texture, self.source.clone(), d.clone()) + .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); + d.set_x(d.x() + size.width() as i32); + } + } + + fn render_children(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + where + T: RenderImage, + { + if !self.expanded { + return; + } + let mut point = dest.top_left() + + Point::new( + self.icon_width as i32 + CHILD_MARGIN, + self.icon_height as i32 + CHILD_MARGIN, + ); + for dir in self.directories.iter() { + let context = RenderContext::RelativePosition(point.clone()); + dir.render(canvas, renderer, &context); + point = point + Point::new(0, dir.height() as i32 + CHILD_MARGIN as i32); + } + for file in self.files.iter() { + let context = RenderContext::RelativePosition(point.clone()); + file.render(canvas, renderer, &context); + point = point + Point::new(0, file.height() as i32 + CHILD_MARGIN as i32); + } + } + + fn calculate_size(&mut self, renderer: &mut Renderer) { + let size = renderer.load_character_size('W'); + self.height = size.height(); + self.icon_height = size.height(); + self.icon_width = size.height(); + self.name_width = 0; + + for c in self.name().chars() { + let size = renderer.load_character_size(c.clone()); + self.name_width += size.width(); + } + + for dir in self.directories.iter_mut() { + self.height = self.height + dir.height() + CHILD_MARGIN as u32; + } + for file in self.files.iter_mut() { + self.height = self.height + file.height() + CHILD_MARGIN as u32; + } + } + + fn name_and_icon_rect(&self) -> Rect { + Rect::new( + self.pos.x(), + self.pos.y(), + self.icon_width + self.name_width + NAME_MARGIN as u32, + self.icon_height, + ) + } +} + +impl ConfigHolder for DirectoryView { + fn config(&self) -> &ConfigAccess { + &self.config + } +} + +#[cfg_attr(tarpaulin, skip)] +impl DirectoryView { + pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) + where + T: RenderImage, + { + let dest = self.dest(); + let move_point = match context { + &RenderContext::RelativePosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let mut dest = move_render_point(move_point, &dest); + self.render_icon::(canvas, renderer, &mut dest); + self.render_name::(canvas, renderer, &mut dest.clone()); + self.render_children::(canvas, renderer, &mut dest); + } + + pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + if self.opened { + for dir in self.directories.iter_mut() { + dir.prepare_ui(renderer); + } + for file in self.files.iter_mut() { + file.prepare_ui(renderer); + } + } + self.calculate_size(renderer); + } +} + +impl Update for DirectoryView { + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { + if !path::Path::new(&self.path).exists() { + return UpdateResult::RefreshFsTree; + } + if self.opened { + for dir in self.directories.iter_mut() { + dir.update(ticks, context); + } + for file in self.files.iter_mut() { + file.update(ticks, context); + } + } + UpdateResult::NoOp + } +} + +impl RenderBox for DirectoryView { + fn render_start_point(&self) -> Point { + self.pos.clone() + } + + fn dest(&self) -> Rect { + Rect::new( + self.pos.x(), + self.pos.y(), + self.icon_width, + self.icon_height, + ) + } +} + +impl ClickHandler for DirectoryView { + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult { + let dest = self.dest(); + let move_point = match context { + &UpdateContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let dest = move_render_point(move_point.clone(), &dest); + + // icon or name is target of click + let icon_or_name = self.name_and_icon_rect(); + if move_render_point(move_point, &icon_or_name).contains_point(point.clone()) { + return UpdateResult::OpenDirectory(self.path.clone()); + } + + if !self.expanded { + return UpdateResult::NoOp; + } + + let mut p = dest.top_left() + + Point::new( + self.icon_width as i32 + CHILD_MARGIN, + self.icon_height as i32 + CHILD_MARGIN, + ); + for dir in self.directories.iter_mut() { + let context = UpdateContext::ParentPosition(p.clone()); + if dir.is_left_click_target(&point, &context) { + return dir.on_left_click(&point, &context); + } + p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); + } + for file in self.files.iter_mut() { + let context = UpdateContext::ParentPosition(p.clone()); + if file.is_left_click_target(&point, &context) { + return file.on_left_click(&point, &context); + } + p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); + } + + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let dest = self.dest(); + let move_point = match context { + UpdateContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let dest = move_render_point(move_point.clone(), &dest); + + // icon or name is target of click + let name_and_icon_rect = self.name_and_icon_rect(); + if move_render_point(move_point.clone(), &name_and_icon_rect).contains_point(point.clone()) + { + return true; + } + if !self.expanded { + return false; + } + let mut p = dest.top_left() + + Point::new( + self.icon_width as i32 + CHILD_MARGIN, + self.icon_height as i32 + CHILD_MARGIN, + ); + // subdirectory is target of click + for dir in self.directories.iter() { + let context = UpdateContext::ParentPosition(p.clone()); + if dir.is_left_click_target(&point, &context) { + return true; + } + p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); + } + // file inside directory is target of click + for file in self.files.iter() { + let context = UpdateContext::ParentPosition(p.clone()); + if file.is_left_click_target(&point, &context) { + return true; + } + p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); + } + false + } +} diff --git a/rider-editor/src/ui/filesystem/file.rs b/rider-editor/src/ui/filesystem/file.rs new file mode 100644 index 0000000..c90cd05 --- /dev/null +++ b/rider-editor/src/ui/filesystem/file.rs @@ -0,0 +1,204 @@ +use crate::app::*; +use crate::renderer::*; +use crate::ui::*; +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use std::collections::HashMap; +use std::path; + +pub struct FileEntry { + name_width: u32, + icon_width: u32, + height: u32, + name: String, + path: String, + dest: Rect, + source: Rect, + config: ConfigAccess, + char_sizes: HashMap, +} + +impl FileEntry { + pub fn new(name: String, path: String, config: ConfigAccess) -> Self { + Self { + name, + path, + name_width: 0, + icon_width: 0, + height: 0, + dest: Rect::new(0, 0, 16, 16), + source: Rect::new(0, 0, 64, 64), + config, + char_sizes: HashMap::new(), + } + } + + pub fn name_width(&self) -> u32 { + self.name_width + } + + pub fn icon_width(&self) -> u32 { + self.icon_width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn path(&self) -> String { + self.path.clone() + } + + pub fn dest(&self) -> &Rect { + &self.dest + } + + pub fn source(&self) -> &Rect { + &self.source + } + + pub fn full_dest(&self) -> Rect { + Rect::new( + self.dest.x(), + self.dest.y(), + self.icon_width + NAME_MARGIN as u32 + self.name_width, + self.height, + ) + } + + fn render_icon(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + where + T: RenderImage, + { + let dir_texture_path = { + let c = self.config.read().unwrap(); + let mut themes_dir = c.directories().themes_dir.clone(); + let path = c.theme().images().file_icon(); + themes_dir.push(path); + themes_dir.to_str().unwrap().to_owned() + }; + let texture = renderer + .texture_manager() + .load(dir_texture_path.as_str()) + .unwrap_or_else(|_| panic!("Failed to load directory entry texture")); + dest.set_width(16); + dest.set_height(16); + canvas + .render_image(texture, self.source.clone(), dest.clone()) + .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); + } + + fn render_name(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + where + T: RenderImage, + { + let mut d = dest.clone(); + d.set_x(dest.x() + NAME_MARGIN); + + let font_details = build_font_details(self); + let font = renderer.font_manager().load(&font_details).unwrap(); + let texture_manager = renderer.texture_manager(); + let name = self.name(); + + for c in name.chars() { + let size = self + .char_sizes + .get(&c) + .unwrap_or(&Rect::new(0, 0, 0, 0)) + .clone(); + let mut text_details = TextDetails { + color: Color::RGBA(255, 255, 255, 0), + text: c.to_string(), + font: font_details.clone(), + }; + let text_texture = texture_manager.load_text(&mut text_details, &font).unwrap(); + d.set_width(size.width()); + d.set_height(size.height()); + + canvas + .render_image(text_texture, self.source.clone(), d.clone()) + .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); + d.set_x(d.x() + size.width() as i32) + } + } +} + +impl ConfigHolder for FileEntry { + fn config(&self) -> &ConfigAccess { + &self.config + } +} + +#[cfg_attr(tarpaulin, skip)] +impl FileEntry { + pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) + where + T: RenderImage, + { + let mut dest = match context { + &RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest.clone(), + }; + self.render_icon(canvas, renderer, &mut dest); + self.render_name(canvas, renderer, &mut dest.clone()); + } + + pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + let w_rect = get_text_character_rect('W', renderer).unwrap(); + self.char_sizes.insert('W', w_rect.clone()); + self.height = w_rect.height(); + self.icon_width = w_rect.height(); + self.name_width = 0; + + for c in self.name().chars() { + let size = { get_text_character_rect(c.clone(), renderer).unwrap() }; + self.char_sizes.insert(c, size); + self.name_width += size.width(); + } + self.dest.set_width(w_rect.height()); + self.dest.set_height(w_rect.height()); + } +} + +impl Update for FileEntry { + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { + if !path::Path::new(&self.path).exists() { + return UpdateResult::RefreshFsTree; + } + UpdateResult::NoOp + } +} + +impl RenderBox for FileEntry { + fn render_start_point(&self) -> Point { + self.dest.top_left() + } + + fn dest(&self) -> Rect { + self.dest.clone() + } +} + +impl ClickHandler for FileEntry { + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult { + UpdateResult::OpenFile(self.path.clone()) + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let dest = Rect::new( + self.dest.x(), + self.dest.y(), + self.icon_width + self.name_width + NAME_MARGIN as u32, + self.dest.height(), + ); + let rect = match context { + UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &dest), + _ => dest, + }; + rect.contains_point(point.clone()) + } +} diff --git a/rider-editor/src/ui/filesystem/mod.rs b/rider-editor/src/ui/filesystem/mod.rs new file mode 100644 index 0000000..9e2843d --- /dev/null +++ b/rider-editor/src/ui/filesystem/mod.rs @@ -0,0 +1,7 @@ +pub use crate::ui::filesystem::directory::*; +pub use crate::ui::filesystem::file::*; + +pub mod directory; +pub mod file; + +pub const NAME_MARGIN: i32 = 20; diff --git a/src/ui/menu_bar.rs b/rider-editor/src/ui/menu_bar.rs similarity index 85% rename from src/ui/menu_bar.rs rename to rider-editor/src/ui/menu_bar.rs index 7c3b45f..021a0ca 100644 --- a/src/ui/menu_bar.rs +++ b/rider-editor/src/ui/menu_bar.rs @@ -1,11 +1,9 @@ use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; use crate::renderer::*; use crate::ui::*; +use rider_config::ConfigAccess; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -use std::rc::Rc; -use std::sync::*; pub struct MenuBar { border_color: Color, @@ -16,12 +14,17 @@ pub struct MenuBar { impl MenuBar { pub fn new(config: ConfigAccess) -> Self { - let (background_color, w, h): (Color, u32, u16) = { + let (background_color, border_color, w, h): (Color, Color, u32, u16) = { let c = config.read().unwrap(); - (c.theme().background().into(), c.width(), c.menu_height()) + ( + c.theme().background().into(), + c.theme().border_color().into(), + c.width(), + c.menu_height(), + ) }; Self { - border_color: Color::RGB(10, 10, 10), + border_color, background_color, dest: Rect::new(0, 0, w as u32, h as u32), config, @@ -33,6 +36,7 @@ impl MenuBar { } } +#[cfg_attr(tarpaulin, skip)] impl Render for MenuBar { fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { use std::borrow::*; @@ -41,23 +45,23 @@ impl Render for MenuBar { canvas.set_draw_color(self.background_color.clone()); canvas .fill_rect(match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()), - _ => self.dest.clone(), + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest(), }) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); - canvas.set_draw_color(self.border_color.clone()); + canvas.set_draw_color(self.border_color); canvas .draw_rect(match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()), - _ => self.dest.clone(), + RenderContext::RelativePosition(p) => move_render_point((*p).clone(), &self.dest), + _ => self.dest(), }) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); } fn prepare_ui(&mut self, _renderer: &mut Renderer) { let width = self.config.read().unwrap().width(); - let height = self.config.read().unwrap().menu_height() as u32; + let height = u32::from(self.config.read().unwrap().menu_height()); self.dest = Rect::new(0, 0, width, height); } } @@ -76,11 +80,11 @@ impl ClickHandler for MenuBar { } fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - let rect = match context.clone() { - UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), - _ => self.dest().clone(), + let rect = match *context { + UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest(), }; - is_in_rect(point, &rect) + rect.contains_point(point.clone()) } } @@ -89,19 +93,17 @@ impl RenderBox for MenuBar { self.dest.top_left() } - fn dest(&self) -> &Rect { - &self.dest + fn dest(&self) -> Rect { + self.dest } } #[cfg(test)] mod test_getters { - use crate::app::*; - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::pixels::Color; + use sdl2::rect::Rect; use std::sync::*; #[test] @@ -129,11 +131,9 @@ mod test_getters { #[cfg(test)] mod test_render_box { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Point; use std::sync::*; #[test] @@ -149,11 +149,9 @@ mod test_render_box { #[cfg(test)] mod test_click_handler { use crate::app::*; - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Point; use std::sync::*; #[test] diff --git a/src/ui/mod.rs b/rider-editor/src/ui/mod.rs similarity index 69% rename from src/ui/mod.rs rename to rider-editor/src/ui/mod.rs index bbfe59f..198ba38 100644 --- a/src/ui/mod.rs +++ b/rider-editor/src/ui/mod.rs @@ -1,14 +1,20 @@ use sdl2::rect::{Point, Rect}; +use sdl2::render::Texture; +use std::rc::Rc; + +use crate::app::application::WindowCanvas; use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; use crate::renderer::managers::*; use crate::renderer::Renderer; +use rider_config::*; pub mod caret; pub mod file; pub mod file_editor; +pub mod filesystem; pub mod menu_bar; +pub mod modal; pub mod project_tree; pub mod scroll_bar; pub mod text_character; @@ -16,7 +22,9 @@ pub mod text_character; pub use crate::ui::caret::*; pub use crate::ui::file::*; pub use crate::ui::file_editor::*; +pub use crate::ui::filesystem::*; pub use crate::ui::menu_bar::*; +pub use crate::ui::modal::*; pub use crate::ui::project_tree::*; pub use crate::ui::scroll_bar::*; pub use crate::ui::text_character::*; @@ -34,6 +42,38 @@ pub enum RenderContext { RelativePosition(Point), } +pub trait RenderRect { + fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; +} + +pub trait RenderBorder { + fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; +} + +pub trait RenderImage { + fn render_image(&mut self, tex: Rc, src: Rect, dest: Rect) -> Result<(), String>; +} + +impl RenderRect for WindowCanvas { + fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { + self.set_draw_color(color); + self.fill_rect(rect) + } +} + +impl RenderBorder for WindowCanvas { + fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { + self.set_draw_color(color); + self.draw_rect(rect) + } +} + +impl RenderImage for WindowCanvas { + fn render_image(&mut self, tex: Rc, src: Rect, dest: Rect) -> Result<(), String> { + self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false) + } +} + #[inline] pub fn is_in_rect(point: &Point, rect: &Rect) -> bool { rect.contains_point(point.clone()) @@ -47,7 +87,7 @@ where let c = config_holder.config().read().unwrap(); FontDetails::new( c.editor_config().font_path().as_str(), - c.editor_config().character_size().clone(), + c.editor_config().character_size(), ) } @@ -55,7 +95,7 @@ pub fn get_text_character_rect<'l, T>(c: char, renderer: &mut T) -> Option where T: ManagersHolder<'l> + ConfigHolder, { - let font_details = build_font_details(renderer); + let font_details = renderer.config().read().unwrap().editor_config().into(); renderer .font_manager() .load(&font_details) @@ -69,8 +109,9 @@ pub fn move_render_point(p: Point, d: &Rect) -> Rect { Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height()) } +#[cfg_attr(tarpaulin, skip)] pub trait Render { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: &RenderContext); + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext); fn prepare_ui(&mut self, renderer: &mut Renderer); } @@ -88,14 +129,13 @@ pub trait ClickHandler { pub trait RenderBox { fn render_start_point(&self) -> Point; - fn dest(&self) -> &Rect; + fn dest(&self) -> Rect; } #[cfg(test)] mod tests { use super::*; use crate::tests::support; - use sdl2::rect::*; struct ConfigWrapper { pub inner: ConfigAccess, diff --git a/rider-editor/src/ui/modal/mod.rs b/rider-editor/src/ui/modal/mod.rs new file mode 100644 index 0000000..f8712d5 --- /dev/null +++ b/rider-editor/src/ui/modal/mod.rs @@ -0,0 +1,3 @@ +pub mod open_file; + +pub use crate::ui::modal::open_file::OpenFile; diff --git a/rider-editor/src/ui/modal/open_file.rs b/rider-editor/src/ui/modal/open_file.rs new file mode 100644 index 0000000..483faf2 --- /dev/null +++ b/rider-editor/src/ui/modal/open_file.rs @@ -0,0 +1,235 @@ +use crate::renderer::Renderer; +use crate::ui::*; +use crate::ui::{RenderContext as RC, UpdateContext as UC}; +use rider_config::ConfigAccess; +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use std::sync::Arc; + +const CONTENT_MARGIN_LEFT: i32 = 16; +const CONTENT_MARGIN_TOP: i32 = 24; +const DEFAULT_ICON_SIZE: u32 = 16; + +pub struct OpenFile { + root_path: String, + directory_view: DirectoryView, + vertical_scroll_bar: VerticalScrollBar, + horizontal_scroll_bar: HorizontalScrollBar, + dest: Rect, + full_dest: Rect, + background_color: Color, + border_color: Color, + config: ConfigAccess, +} + +impl OpenFile { + pub fn new(root_path: String, width: u32, height: u32, config: ConfigAccess) -> Self { + let (window_width, window_height, background_color, border_color) = { + let c = config.read().unwrap(); + let theme = c.theme(); + ( + c.width(), + c.height(), + theme.background().into(), + theme.border_color().into(), + ) + }; + Self { + directory_view: DirectoryView::new(root_path.clone(), Arc::clone(&config)), + vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), + horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)), + root_path, + dest: Rect::new( + (window_width / 2) as i32 - (width / 2) as i32, + (window_height / 2) as i32 - (height / 2) as i32, + width, + height, + ), + full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), + background_color, + border_color, + config, + } + } + + pub fn root_path(&self) -> String { + self.root_path.clone() + } + + pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) { + self.directory_view.open_directory(dir_path, renderer); + { + let dest = self.directory_view.dest(); + let full_dest = Rect::new( + dest.x(), + dest.y(), + dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), + dest.height() + (2 * CONTENT_MARGIN_TOP as u32), + ); + self.full_dest = full_dest; + } + } + + pub fn full_rect(&self) -> &Rect { + &self.full_dest + } +} + +impl ScrollableView for OpenFile { + fn scroll_by(&mut self, x: i32, y: i32) { + let read_config = self.config.read().unwrap(); + + let value_x = read_config.scroll().speed() * x; + let value_y = read_config.scroll().speed() * y; + let old_x = self.horizontal_scroll_bar.scroll_value(); + let old_y = self.vertical_scroll_bar.scroll_value(); + + if value_x + old_x >= 0 { + self.horizontal_scroll_bar.scroll_to(value_x + old_x); + if self.horizontal_scroll_bar.scrolled_part() > 1.0 { + self.horizontal_scroll_bar.scroll_to(old_x); + } + } + if value_y + old_y >= 0 { + self.vertical_scroll_bar.scroll_to(value_y + old_y); + if self.vertical_scroll_bar.scrolled_part() > 1.0 { + self.vertical_scroll_bar.scroll_to(old_y); + } + } + } + + fn scroll(&self) -> Point { + Point::new( + -self.horizontal_scroll_bar.scroll_value(), + -self.vertical_scroll_bar.scroll_value(), + ) + } +} + +impl Update for OpenFile { + fn update(&mut self, ticks: i32, context: &UC) -> UR { + let (window_width, window_height, color, scroll_width, scroll_margin) = { + let c = self.config.read().unwrap(); + ( + c.width(), + c.height(), + c.theme().background().into(), + c.scroll().width(), + c.scroll().margin_right(), + ) + }; + + self.dest + .set_x((window_width / 2) as i32 - (self.dest.width() / 2) as i32); + self.dest + .set_y((window_height / 2) as i32 - (self.dest.height() / 2) as i32); + self.background_color = color; + + // Scroll bars + self.vertical_scroll_bar + .set_full_size(self.full_dest.height()); // full dest + self.vertical_scroll_bar.set_viewport(self.dest.height()); + self.vertical_scroll_bar + .set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin)); + self.vertical_scroll_bar.update(ticks, context); + + self.horizontal_scroll_bar + .set_full_size(self.full_dest.width()); // full dest + self.horizontal_scroll_bar.set_viewport(self.dest.width()); + self.horizontal_scroll_bar + .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); + self.horizontal_scroll_bar.update(ticks, context); + + // End + UR::NoOp + } +} + +#[cfg_attr(tarpaulin, skip)] +impl OpenFile { + pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RC) + where + T: RenderRect + RenderBorder + RenderImage, + { + let dest = match context { + RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest, + }; + + // Background + // canvas.set_clip_rect(dest.clone()); + canvas + .render_rect(dest, self.background_color) + .unwrap_or_else(|_| panic!("Failed to render open file modal background!")); + canvas + .render_border(dest, self.border_color) + .unwrap_or_else(|_| panic!("Failed to render open file modal border!")); + + let context = RC::RelativePosition( + dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(), + ); + + // directory tree + self.directory_view.render(canvas, renderer, &context); + + // Scroll bars + self.vertical_scroll_bar.render( + canvas, + &RenderContext::RelativePosition(self.dest.top_left()), + ); + self.horizontal_scroll_bar.render( + canvas, + &RenderContext::RelativePosition(self.dest.top_left()), + ); + } + + pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + self.directory_view.prepare_ui(renderer); + } +} + +impl RenderBox for OpenFile { + fn render_start_point(&self) -> Point { + self.dest.top_left() + } + + fn dest(&self) -> Rect { + self.dest.clone() + } +} + +impl ClickHandler for OpenFile { + fn on_left_click(&mut self, point: &Point, context: &UC) -> UR { + let dest = match context { + UC::ParentPosition(p) => move_render_point(*p, &self.dest), + _ => self.dest, + }; + let context = UC::ParentPosition( + dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(), + ); + let res = self.directory_view.on_left_click(point, &context); + { + let dest = self.directory_view.dest(); + let full_dest = Rect::new( + dest.x(), + dest.y(), + dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), + dest.height() + (2 * CONTENT_MARGIN_TOP as u32), + ); + self.full_dest = full_dest; + } + res + } + + fn is_left_click_target(&self, point: &Point, context: &UC) -> bool { + let dest = match context { + UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest.clone(), + }; + let context = UC::ParentPosition( + dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(), + ); + self.directory_view.is_left_click_target(point, &context); + true + } +} diff --git a/src/ui/project_tree/mod.rs b/rider-editor/src/ui/project_tree/mod.rs similarity index 100% rename from src/ui/project_tree/mod.rs rename to rider-editor/src/ui/project_tree/mod.rs diff --git a/src/ui/scroll_bar/horizontal_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs similarity index 89% rename from src/ui/scroll_bar/horizontal_scroll_bar.rs rename to rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs index d06da6f..fdcc8a4 100644 --- a/src/ui/scroll_bar/horizontal_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs @@ -1,9 +1,8 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; -use crate::renderer::*; +use crate::app::UpdateResult as UR; use crate::ui::*; -use sdl2::pixels::*; -use sdl2::rect::*; +use rider_config::ConfigAccess; +use sdl2::pixels::Color; +use sdl2::rect::Rect; pub struct HorizontalScrollBar { scroll_value: i32, @@ -54,22 +53,26 @@ impl Update for HorizontalScrollBar { } } -impl Render for HorizontalScrollBar { - fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { +#[cfg_attr(tarpaulin, skip)] +impl HorizontalScrollBar { + pub fn render(&self, canvas: &mut T, context: &RenderContext) + where + T: RenderRect, + { if self.full_width < self.viewport { return; } - canvas.set_draw_color(Color::RGBA(255, 255, 255, 0)); canvas - .fill_rect(match context { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), - _ => self.rect.clone(), - }) + .render_rect( + match context { + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), + _ => self.rect.clone(), + }, + Color::RGBA(255, 255, 255, 0), + ) .unwrap_or_else(|_| panic!("Failed to render vertical scroll back")); } - - fn prepare_ui(&mut self, _renderer: &mut Renderer) {} } impl Scrollable for HorizontalScrollBar { diff --git a/src/ui/scroll_bar/mod.rs b/rider-editor/src/ui/scroll_bar/mod.rs similarity index 68% rename from src/ui/scroll_bar/mod.rs rename to rider-editor/src/ui/scroll_bar/mod.rs index 3ab32b1..7013b94 100644 --- a/src/ui/scroll_bar/mod.rs +++ b/rider-editor/src/ui/scroll_bar/mod.rs @@ -1,9 +1,9 @@ +pub use crate::ui::scroll_bar::horizontal_scroll_bar::HorizontalScrollBar; +pub use crate::ui::scroll_bar::vertical_scroll_bar::VerticalScrollBar; + pub mod horizontal_scroll_bar; pub mod vertical_scroll_bar; -use crate::ui::scroll_bar::horizontal_scroll_bar::*; -use crate::ui::scroll_bar::vertical_scroll_bar::*; - pub trait Scrollable { fn scroll_to(&mut self, n: i32); diff --git a/src/ui/scroll_bar/vertical_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs similarity index 88% rename from src/ui/scroll_bar/vertical_scroll_bar.rs rename to rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs index bf51e5e..2655db2 100644 --- a/src/ui/scroll_bar/vertical_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs @@ -1,9 +1,8 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; -use crate::renderer::*; +use crate::app::UpdateResult as UR; use crate::ui::*; -use sdl2::pixels::*; -use sdl2::rect::*; +use rider_config::ConfigAccess; +use sdl2::pixels::Color; +use sdl2::rect::Rect; pub struct VerticalScrollBar { scroll_value: i32, @@ -54,22 +53,26 @@ impl Update for VerticalScrollBar { } } -impl Render for VerticalScrollBar { - fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { +#[cfg_attr(tarpaulin, skip)] +impl VerticalScrollBar { + pub fn render(&self, canvas: &mut T, context: &RenderContext) + where + T: RenderBorder, + { if self.full_height() < self.viewport() { return; } - canvas.set_draw_color(Color::RGBA(255, 255, 255, 0)); canvas - .fill_rect(match context { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), - _ => self.rect.clone(), - }) + .render_border( + match context { + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), + _ => self.rect.clone(), + }, + Color::RGBA(255, 255, 255, 0), + ) .unwrap_or_else(|_| panic!("Failed to render vertical scroll back")); } - - fn prepare_ui(&mut self, _renderer: &mut Renderer) {} } impl Scrollable for VerticalScrollBar { @@ -94,6 +97,9 @@ impl Scrollable for VerticalScrollBar { } fn scrolled_part(&self) -> f64 { + if self.full_height() <= self.viewport() { + return 1.0; + } self.scroll_value().abs() as f64 / (self.full_height() - self.viewport()) as f64 } } diff --git a/src/ui/text_character.rs b/rider-editor/src/ui/text_character.rs similarity index 80% rename from src/ui/text_character.rs rename to rider-editor/src/ui/text_character.rs index c445f6d..8d400fb 100644 --- a/src/ui/text_character.rs +++ b/rider-editor/src/ui/text_character.rs @@ -1,17 +1,16 @@ use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::config::*; -use crate::lexer::TokenType; use crate::renderer::managers::*; use crate::renderer::*; use crate::ui::caret::CaretPosition; use crate::ui::*; +use rider_config::{ConfigAccess, ConfigHolder}; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -use sdl2::render::Texture; -use sdl2::ttf::Font; -use std::rc::Rc; -use std::sync::*; + +pub trait CharacterSizeManager { + fn load_character_size(&mut self, c: char) -> Rect; +} #[derive(Clone, Debug)] pub struct TextCharacter { @@ -69,10 +68,10 @@ impl TextCharacter { pub fn update_position(&mut self, current: &mut Rect) { if self.is_new_line() { let y = self.source.height() as i32; - current.set_x(0); - current.set_y(current.y() + y); self.dest.set_x(current.x()); self.dest.set_y(current.y()); + current.set_x(0); + current.set_y(current.y() + y); } else { self.dest.set_x(current.x()); self.dest.set_y(current.y()); @@ -100,25 +99,18 @@ impl TextCharacter { } } -impl Render for TextCharacter { +#[cfg_attr(tarpaulin, skip)] +impl TextCharacter { /** * Must first create targets so even if new line appear renderer will know * where move render starting point */ - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { + pub fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { if self.is_new_line() { return; } - let font_details = { - let config = renderer.config().read().unwrap(); - let ec = config.editor_config(); - FontDetails::new(ec.font_path().as_str(), ec.character_size().clone()) - }; - let font = renderer - .font_manager() - .load(&font_details) - .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); + let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); let c = self.text_character.clone(); let mut details = TextDetails { @@ -127,9 +119,14 @@ impl Render for TextCharacter { font: font_details.clone(), }; let dest = match context { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()), - _ => self.dest.clone(), + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest(), }; + + let font = renderer + .font_manager() + .load(&font_details) + .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) { canvas .copy_ex( @@ -148,27 +145,26 @@ impl Render for TextCharacter { // canvas.draw_rect(dest.clone()).unwrap(); } - fn prepare_ui(&mut self, renderer: &mut Renderer) { - let font_details = build_font_details(renderer); + pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) + where + T: ConfigHolder + CharacterSizeManager + ManagersHolder<'l>, + { + let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); - let font = renderer - .font_manager() - .load(&font_details) - .unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); + let rect = renderer.load_character_size(self.text_character); + self.set_source(&rect); + self.set_dest(&rect); - let c = match self.text_character { - '\n' => 'W', - c => c, - }; - if let Some(rect) = get_text_character_rect(c, renderer) { - self.set_source(&rect); - self.set_dest(&rect); - } let mut details = TextDetails { text: self.text_character.to_string(), color: self.color.clone(), font: font_details.clone(), }; + + let font = renderer + .font_manager() + .load(&font_details) + .unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); renderer .texture_manager() .load_text(&mut details, &font) @@ -191,13 +187,11 @@ impl ClickHandler for TextCharacter { } fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - is_in_rect( - point, - &match context { - &UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), - _ => self.dest().clone(), - }, - ) + let rect = match context { + &UpdateContext::ParentPosition(p) => move_render_point(p, &self.dest), + _ => self.dest(), + }; + is_in_rect(point, &rect) } } @@ -206,18 +200,17 @@ impl RenderBox for TextCharacter { self.dest.top_left() } - fn dest(&self) -> &Rect { - &self.dest + fn dest(&self) -> Rect { + self.dest } } #[cfg(test)] mod test_getters { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::pixels::Color; + use sdl2::rect::Rect; use std::sync::*; #[test] @@ -323,7 +316,7 @@ mod test_getters { Color::RGB(1, 12, 123), Arc::clone(&config), ); - assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0)); + assert_eq!(widget.dest(), Rect::new(0, 0, 0, 0)); } #[test] @@ -343,38 +336,48 @@ mod test_getters { #[cfg(test)] mod test_own_methods { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::Rect; use std::sync::*; #[test] fn must_update_position_of_new_line() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let mut current = Rect::new(10, 23, 0, 0); widget.update_position(&mut current); assert_eq!(current, Rect::new(0, 103, 1, 1)); - assert_eq!(widget.dest(), &Rect::new(0, 103, 30, 40)); + assert_eq!(widget.dest(), Rect::new(10, 23, 30, 40)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80)); } #[test] fn must_update_position_of_non_new_line() { let config = support::build_config(); - let mut widget = - TextCharacter::new('W', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + 'W', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let mut current = Rect::new(10, 23, 0, 0); widget.update_position(&mut current); assert_eq!(current, Rect::new(80, 23, 1, 1)); - assert_eq!(widget.dest(), &Rect::new(10, 23, 70, 80)); + assert_eq!(widget.dest(), Rect::new(10, 23, 70, 80)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80)); } } @@ -382,18 +385,22 @@ mod test_own_methods { #[cfg(test)] mod test_click_handler { use crate::app::*; - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::{Point, Rect}; use std::sync::*; #[test] fn refute_when_not_click_target() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let point = Point::new(0, 0); @@ -405,8 +412,14 @@ mod test_click_handler { #[test] fn assert_when_click_target() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let point = Point::new(20, 30); @@ -418,8 +431,14 @@ mod test_click_handler { #[test] fn refute_when_not_click_target_because_parent() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let point = Point::new(20, 30); @@ -431,8 +450,14 @@ mod test_click_handler { #[test] fn assert_when_click_target_because_parent() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let point = Point::new(120, 130); @@ -451,7 +476,7 @@ mod test_click_handler { position.clone(), line.clone(), true, - Color::RGB(0, 0, 0), + sdl2::pixels::Color::RGB(0, 0, 0), Arc::clone(&config), ); let dest = Rect::new(10, 20, 30, 40); @@ -468,18 +493,22 @@ mod test_click_handler { #[cfg(test)] mod test_render_box { - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::{Point, Rect}; use std::sync::*; #[test] fn must_return_top_left_point() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let result = widget.render_start_point(); @@ -491,18 +520,22 @@ mod test_render_box { #[cfg(test)] mod test_update { use crate::app::*; - use crate::renderer::*; use crate::tests::*; use crate::ui::*; - use sdl2::pixels::*; - use sdl2::rect::*; + use sdl2::rect::{Point, Rect}; use std::sync::*; #[test] fn assert_do_nothing() { let config = support::build_config(); - let mut widget = - TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); + let mut widget = TextCharacter::new( + '\n', + 0, + 0, + true, + sdl2::pixels::Color::RGB(0, 0, 0), + Arc::clone(&config), + ); widget.set_dest(&Rect::new(10, 20, 30, 40)); widget.set_source(&Rect::new(50, 60, 70, 80)); let result = widget.update( diff --git a/rider-generator/Cargo.toml b/rider-generator/Cargo.toml new file mode 100644 index 0000000..a2750b2 --- /dev/null +++ b/rider-generator/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rider-generator" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +rider-config = { path = "../rider-config", version = "0.1.0" } +rider-themes = { path = "../rider-themes", version = "0.1.0" } +log = "*" +simplelog = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +dirs = "*" +uuid = { version = "0.7", features = ["v4"] } +rand = "0.5" diff --git a/assets/fonts/Beyond Wonderland.ttf b/rider-generator/assets/fonts/Beyond Wonderland.ttf similarity index 100% rename from assets/fonts/Beyond Wonderland.ttf rename to rider-generator/assets/fonts/Beyond Wonderland.ttf diff --git a/assets/fonts/DejaVuSansMono.ttf b/rider-generator/assets/fonts/DejaVuSansMono.ttf similarity index 100% rename from assets/fonts/DejaVuSansMono.ttf rename to rider-generator/assets/fonts/DejaVuSansMono.ttf diff --git a/assets/fonts/ElaineSans-Medium.ttf b/rider-generator/assets/fonts/ElaineSans-Medium.ttf similarity index 100% rename from assets/fonts/ElaineSans-Medium.ttf rename to rider-generator/assets/fonts/ElaineSans-Medium.ttf diff --git a/rider-generator/assets/themes/default/images/directory-48x48.png b/rider-generator/assets/themes/default/images/directory-48x48.png new file mode 100644 index 0000000..d1d5462 Binary files /dev/null and b/rider-generator/assets/themes/default/images/directory-48x48.png differ diff --git a/rider-generator/assets/themes/default/images/directory-512x512.png b/rider-generator/assets/themes/default/images/directory-512x512.png new file mode 100644 index 0000000..deaa13a Binary files /dev/null and b/rider-generator/assets/themes/default/images/directory-512x512.png differ diff --git a/rider-generator/assets/themes/default/images/directory-64x64.png b/rider-generator/assets/themes/default/images/directory-64x64.png new file mode 100644 index 0000000..e18109b Binary files /dev/null and b/rider-generator/assets/themes/default/images/directory-64x64.png differ diff --git a/rider-generator/assets/themes/default/images/file-48x48.png b/rider-generator/assets/themes/default/images/file-48x48.png new file mode 100644 index 0000000..7e0d8a5 Binary files /dev/null and b/rider-generator/assets/themes/default/images/file-48x48.png differ diff --git a/rider-generator/assets/themes/default/images/file-512x512.png b/rider-generator/assets/themes/default/images/file-512x512.png new file mode 100644 index 0000000..899a179 Binary files /dev/null and b/rider-generator/assets/themes/default/images/file-512x512.png differ diff --git a/rider-generator/assets/themes/default/images/file-64x64.png b/rider-generator/assets/themes/default/images/file-64x64.png new file mode 100644 index 0000000..63a2ea5 Binary files /dev/null and b/rider-generator/assets/themes/default/images/file-64x64.png differ diff --git a/rider-generator/assets/themes/railscasts/images/directory-48x48.png b/rider-generator/assets/themes/railscasts/images/directory-48x48.png new file mode 100644 index 0000000..d4c959d Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/directory-48x48.png differ diff --git a/rider-generator/assets/themes/railscasts/images/directory-512x512.png b/rider-generator/assets/themes/railscasts/images/directory-512x512.png new file mode 100644 index 0000000..deaa13a Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/directory-512x512.png differ diff --git a/rider-generator/assets/themes/railscasts/images/directory-64x64.png b/rider-generator/assets/themes/railscasts/images/directory-64x64.png new file mode 100644 index 0000000..d6ee7c3 Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/directory-64x64.png differ diff --git a/rider-generator/assets/themes/railscasts/images/file-48x48.png b/rider-generator/assets/themes/railscasts/images/file-48x48.png new file mode 100644 index 0000000..721382a Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/file-48x48.png differ diff --git a/rider-generator/assets/themes/railscasts/images/file-512x512.png b/rider-generator/assets/themes/railscasts/images/file-512x512.png new file mode 100644 index 0000000..899a179 Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/file-512x512.png differ diff --git a/rider-generator/assets/themes/railscasts/images/file-64x64.png b/rider-generator/assets/themes/railscasts/images/file-64x64.png new file mode 100644 index 0000000..34030e8 Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/file-64x64.png differ diff --git a/rider-generator/src/config.rs b/rider-generator/src/config.rs new file mode 100644 index 0000000..905fbe7 --- /dev/null +++ b/rider-generator/src/config.rs @@ -0,0 +1,102 @@ +use crate::images; +use rider_config::directories::*; +use std::fs; +use std::path::PathBuf; + +pub fn create(directories: &Directories) -> std::io::Result<()> { + if !directories.themes_dir.exists() { + fs::create_dir_all(&directories.themes_dir)?; + images::create(directories)?; + } + + if !directories.fonts_dir.exists() { + fs::create_dir_all(&directories.fonts_dir)?; + } + write_default_fonts(directories)?; + + if !directories.log_dir.exists() { + fs::create_dir_all(&directories.log_dir)?; + } + + if !directories.project_dir.exists() { + fs::create_dir_all(&directories.project_dir)?; + } + Ok(()) +} + +fn write_default_fonts(directories: &Directories) -> std::io::Result<()> { + let path = directories.fonts_dir.clone().to_str().unwrap().to_owned(); + let mut buf = PathBuf::new(); + buf.push(path); + buf.push("DejaVuSansMono.ttf"); + if !buf.exists() { + let contents = include_bytes!("../assets/fonts/DejaVuSansMono.ttf"); + fs::write(buf, contents.to_vec())?; + } + + let path = directories.fonts_dir.clone().to_str().unwrap().to_owned(); + let mut buf = PathBuf::new(); + buf.push(path); + buf.push("ElaineSans-Medium.ttf"); + if !buf.exists() { + let contents = include_bytes!("../assets/fonts/ElaineSans-Medium.ttf"); + fs::write(buf, contents.to_vec())?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::create_dir_all; + use std::path::Path; + use uuid::Uuid; + + #[cfg(test)] + fn join(a: String, b: String) -> String { + vec![a, b].join("/") + } + + #[test] + fn assert_create_fonts() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new(join(test_path.clone(), "rider/fonts".to_owned()).as_str()).exists(), + true + ); + } + + #[test] + fn assert_create_log() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new(join(test_path.clone(), "rider/log".to_owned()).as_str()).exists(), + true + ); + } + + #[test] + fn assert_create_themes() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + assert_eq!( + Path::new(join(test_path.clone(), "rider/themes".to_owned()).as_str()).exists(), + false + ); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new(join(test_path.clone(), "rider/themes".to_owned()).as_str()).exists(), + true + ); + } +} diff --git a/rider-generator/src/images.rs b/rider-generator/src/images.rs new file mode 100644 index 0000000..5726483 --- /dev/null +++ b/rider-generator/src/images.rs @@ -0,0 +1,218 @@ +use crate::write_bytes_to::write_bytes_to; +use rider_config::directories::*; +use std::fs::create_dir_all; +use std::path::PathBuf; + +pub fn create(directories: &Directories) -> std::io::Result<()> { + default_theme(directories)?; + railscasts_theme(directories)?; + Ok(()) +} + +fn create_default_directory_icon(dir: &PathBuf) -> std::io::Result<()> { + let blob = include_bytes!("../assets/themes/default/images/directory-64x64.png"); + write_bytes_to(dir, "directory-64x64.png", blob)?; + Ok(()) +} + +fn create_default_file_icon(dir: &PathBuf) -> std::io::Result<()> { + let blob = include_bytes!("../assets/themes/default/images/file-64x64.png"); + write_bytes_to(dir, "file-64x64.png", blob)?; + Ok(()) +} + +fn default_theme(directories: &Directories) -> std::io::Result<()> { + let mut dir = PathBuf::new(); + dir.push(directories.themes_dir.clone()); + dir.push("default"); + dir.push("images"); + let r = create_dir_all(&dir); + #[cfg_attr(tarpaulin, skip)] + r.unwrap_or_else(|_| panic!("Cannot create themes config directory")); + + create_default_directory_icon(&dir)?; + create_default_file_icon(&dir)?; + Ok(()) +} + +fn create_railscasts_directory_icon(dir: &PathBuf) -> std::io::Result<()> { + let blob = include_bytes!("../assets/themes/railscasts/images/directory-64x64.png"); + write_bytes_to(dir, "directory-64x64.png", blob)?; + Ok(()) +} + +fn create_railscasts_file_icon(dir: &PathBuf) -> std::io::Result<()> { + let blob = include_bytes!("../assets/themes/railscasts/images/file-64x64.png"); + write_bytes_to(dir, "file-64x64.png", blob)?; + Ok(()) +} + +fn railscasts_theme(directories: &Directories) -> std::io::Result<()> { + let mut dir = PathBuf::new(); + dir.push(directories.themes_dir.clone()); + dir.push("railscasts"); + dir.push("images"); + create_dir_all(&dir)?; + create_railscasts_directory_icon(&dir)?; + create_railscasts_file_icon(&dir)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::create_dir_all; + use std::path::{Path, PathBuf}; + use uuid::Uuid; + + #[cfg(test)] + fn join(a: String, b: String) -> String { + vec![a, b].join("/") + } + + #[test] + fn assert_create() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + let themes_dir = join(test_path.clone(), "rider/themes".to_owned()); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "railscasts/images/directory-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + false + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "railscasts/images/file-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + false + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "default/images/directory-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + false + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "default/images/file-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + false + ); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "railscasts/images/directory-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + true + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "railscasts/images/file-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + true + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "default/images/directory-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + true + ); + assert_eq!( + Path::new( + join( + themes_dir.clone(), + "default/images/file-64x64.png".to_owned() + ) + .as_str() + ) + .exists(), + true + ); + } + + #[test] + fn assert_create_default_directory_icon() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let file_path: String = join(test_path.clone(), "directory-64x64.png".to_owned()); + let dir: PathBuf = test_path.into(); + assert_eq!(Path::new(file_path.as_str()).exists(), false); + assert_eq!(create_default_directory_icon(&dir).is_ok(), true); + assert_eq!(Path::new(file_path.as_str()).exists(), true); + } + + #[test] + fn assert_create_default_file_icon() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let file_path: String = join(test_path.clone(), "file-64x64.png".to_owned()); + let dir: PathBuf = test_path.into(); + assert_eq!(Path::new(file_path.as_str()).exists(), false); + assert_eq!(create_default_file_icon(&dir).is_ok(), true); + assert_eq!(Path::new(file_path.as_str()).exists(), true); + } + + #[test] + fn assert_create_railscasts_directory_icon() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let file_path: String = join(test_path.clone(), "directory-64x64.png".to_owned()); + let dir: PathBuf = test_path.into(); + assert_eq!(Path::new(file_path.as_str()).exists(), false); + assert_eq!(create_railscasts_directory_icon(&dir).is_ok(), true); + assert_eq!(Path::new(file_path.as_str()).exists(), true); + } + + #[test] + fn assert_create_railscasts_file_icon() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let file_path: String = join(test_path.clone(), "file-64x64.png".to_owned()); + let dir: PathBuf = test_path.into(); + assert_eq!(Path::new(file_path.as_str()).exists(), false); + assert_eq!(create_railscasts_file_icon(&dir).is_ok(), true); + assert_eq!(Path::new(file_path.as_str()).exists(), true); + } +} diff --git a/rider-generator/src/main.rs b/rider-generator/src/main.rs new file mode 100644 index 0000000..906c4ac --- /dev/null +++ b/rider-generator/src/main.rs @@ -0,0 +1,120 @@ +extern crate dirs; +extern crate log; +extern crate rand; +extern crate rider_config; +extern crate rider_themes; +extern crate serde; +extern crate serde_derive; +extern crate serde_json; +extern crate simplelog; +extern crate uuid; + +use rider_config::directories::Directories; + +pub mod config; +pub mod images; +pub mod themes; +pub mod write_bytes_to; + +fn main() -> std::io::Result<()> { + let directories = Directories::new(None, None); + config::create(&directories)?; + themes::create(&directories)?; + images::create(&directories)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env::set_var; + use std::fs::create_dir_all; + use std::path::Path; + use uuid::Uuid; + + #[cfg(test)] + fn exists(dir: &String, sub: &str) -> bool { + let joined = join(dir.clone(), sub.to_owned()); + Path::new(joined.as_str()).exists() + } + + #[cfg(test)] + fn join(a: String, b: String) -> String { + vec![a, b].join("/") + } + + #[test] + fn assert_main() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + let test_path = joined.as_str(); + create_dir_all(test_path.to_owned()).unwrap(); + + set_var("XDG_CONFIG_HOME", test_path); + set_var("XDG_RUNTIME_DIR", test_path); + + assert_eq!(exists(&test_path.to_owned(), ".rider"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&test_path.to_owned(), ".rider"), true); + } + + #[test] + fn assert_fonts_dir() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(joined.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", joined.as_str().clone()); + set_var("XDG_RUNTIME_HOME", joined.as_str().clone()); + assert_eq!(exists(&joined, "rider/fonts"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&joined, "rider/fonts"), true); + } + + #[test] + fn assert_log_dir() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(joined.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", joined.as_str().clone()); + set_var("XDG_RUNTIME_HOME", joined.as_str().clone()); + assert_eq!(exists(&joined, "rider/log"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&joined, "rider/log"), true); + } + + #[test] + fn assert_themes_dir() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(joined.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", joined.as_str().clone()); + set_var("XDG_RUNTIME_HOME", joined.as_str().clone()); + assert_eq!(exists(&joined, "rider/themes"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&joined, "rider/themes"), true); + } + + #[test] + fn assert_default_json() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(joined.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", joined.as_str().clone()); + set_var("XDG_RUNTIME_HOME", joined.as_str().clone()); + assert_eq!(exists(&joined, "rider/themes/default.json"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&joined, "rider/themes/default.json"), true); + } + + #[test] + fn assert_railscasts_json() { + let uniq = Uuid::new_v4(); + let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(joined.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", joined.as_str().clone()); + set_var("XDG_RUNTIME_HOME", joined.as_str().clone()); + assert_eq!(exists(&joined, "rider/themes/railscasts.json"), false); + assert_eq!(main().is_ok(), true); + assert_eq!(exists(&joined, "rider/themes/railscasts.json"), true); + } +} diff --git a/rider-generator/src/themes.rs b/rider-generator/src/themes.rs new file mode 100644 index 0000000..92b2e03 --- /dev/null +++ b/rider-generator/src/themes.rs @@ -0,0 +1,81 @@ +use crate::*; +use rider_themes::predef::*; +use rider_themes::Theme; +use std::fs; +use std::path::PathBuf; + +pub fn create(directories: &Directories) -> std::io::Result<()> { + fs::create_dir_all(directories.themes_dir.clone())?; + for theme in default_styles() { + write_theme(&theme, directories)?; + } + Ok(()) +} + +fn write_theme(theme: &Theme, directories: &Directories) -> std::io::Result<()> { + let mut theme_path = PathBuf::new(); + theme_path.push(directories.themes_dir.clone()); + theme_path.push(format!("{}.json", theme.name())); + let contents = serde_json::to_string_pretty(&theme).unwrap(); + fs::write(&theme_path, contents.clone())?; + Ok(()) +} + +fn default_styles() -> Vec { + vec![default::build_theme(), railscasts::build_theme()] +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::create_dir_all; + use std::path::Path; + use uuid::Uuid; + + #[test] + fn assert_default_styles() { + assert_eq!(default_styles().len(), 2); + } + + #[cfg(test)] + fn join(a: String, b: String) -> String { + vec![a, b].join("/") + } + + #[test] + fn assert_create_default() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + let rider_dir = join(test_path.clone(), "rider".to_owned()); + assert_eq!( + Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(), + false + ); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(), + true + ); + } + + #[test] + fn assert_create_railscasts() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + let directories = Directories::new(Some(test_path.clone()), None); + let rider_dir = join(test_path.clone(), "rider".to_owned()); + assert_eq!( + Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(), + false + ); + assert_eq!(create(&directories).is_ok(), true); + assert_eq!( + Path::new(join(rider_dir.clone(), "themes/railscasts.json".to_owned()).as_str()) + .exists(), + true + ); + } +} diff --git a/rider-generator/src/write_bytes_to.rs b/rider-generator/src/write_bytes_to.rs new file mode 100644 index 0000000..616b39b --- /dev/null +++ b/rider-generator/src/write_bytes_to.rs @@ -0,0 +1,34 @@ +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +pub fn write_bytes_to(dir: &PathBuf, file: &str, blob: &[u8]) -> std::io::Result<()> { + let mut path = dir.clone(); + path.push(file); + let mut f = File::create(path.to_str().unwrap())?; + f.write_all(blob)?; + f.flush()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env::temp_dir; + use std::path::Path; + use uuid::Uuid; + + #[test] + fn must_create_file() { + let test_dir = temp_dir(); + let file_name = Uuid::new_v4().to_string(); + let blob: Vec = vec![1, 2, 3, 4]; + let res = write_bytes_to(&test_dir, file_name.as_str(), blob.as_slice()); + assert_eq!(res.is_ok(), true); + + let mut test_file_path = test_dir.clone(); + test_file_path.push(file_name); + let file_path = Path::new(&test_file_path); + assert_eq!(file_path.exists(), true); + } +} diff --git a/rider-lexers/Cargo.toml b/rider-lexers/Cargo.toml new file mode 100644 index 0000000..bdd7b11 --- /dev/null +++ b/rider-lexers/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rider-lexers" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +plex = "*" +log = "*" +simplelog = "*" diff --git a/src/lexer/mod.rs b/rider-lexers/src/lib.rs similarity index 90% rename from src/lexer/mod.rs rename to rider-lexers/src/lib.rs index f954299..72bc94a 100644 --- a/src/lexer/mod.rs +++ b/rider-lexers/src/lib.rs @@ -1,3 +1,6 @@ +extern crate log; +extern crate simplelog; + use std::ops::Deref; pub mod plain; @@ -114,19 +117,19 @@ impl Token { } pub fn line(&self) -> usize { - self.line.clone() + self.line } pub fn character(&self) -> usize { - self.character.clone() + self.character } pub fn start(&self) -> usize { - self.start.clone() + self.start } pub fn end(&self) -> usize { - self.end.clone() + self.end } pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { @@ -140,14 +143,14 @@ impl Token { } } -pub fn parse(text: String, language: &Language) -> Vec { +pub fn parse(text: String, language: Language) -> Vec { match language { - &Language::PlainText => plain::lexer::Lexer::new(text.as_str()) - .inspect(|tok| warn!("tok: {:?}", tok)) + Language::PlainText => plain::lexer::Lexer::new(text.as_str()) + // .inspect(|tok| warn!("tok: {:?}", tok)) .map(|t| t.0) .collect(), - &Language::Rust => rust_lang::lexer::Lexer::new(text.as_str()) - .inspect(|tok| warn!("tok: {:?}", tok)) + Language::Rust => rust_lang::lexer::Lexer::new(text.as_str()) + // .inspect(|tok| warn!("tok: {:?}", tok)) .map(|t| t.0) .collect(), } @@ -155,12 +158,13 @@ pub fn parse(text: String, language: &Language) -> Vec { #[cfg(test)] mod tests { - use crate::lexer::*; + use super::*; + use crate::Token; #[test] fn must_parse_plain() { let buffer = "foo bar"; - let language = &Language::PlainText; + let language = Language::PlainText; let result = parse(buffer.to_string(), language); assert_eq!(result.len(), 3); } @@ -168,7 +172,7 @@ mod tests { #[test] fn must_parse_rust() { let buffer = "foo bar"; - let language = &Language::Rust; + let language = Language::Rust; let result = parse(buffer.to_string(), language); assert_eq!(result.len(), 3); } diff --git a/src/lexer/plain.rs b/rider-lexers/src/plain.rs similarity index 83% rename from src/lexer/plain.rs rename to rider-lexers/src/plain.rs index 4244719..8527b3c 100644 --- a/src/lexer/plain.rs +++ b/rider-lexers/src/plain.rs @@ -1,7 +1,5 @@ -use crate::lexer::{Token, TokenType}; - pub mod lexer { - use crate::lexer::{Span, Token, TokenType}; + use crate::{Span, Token, TokenType}; use plex::lexer; lexer! { @@ -10,6 +8,7 @@ pub mod lexer { r"[ \t\r\n]" => (TokenType::Whitespace { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), + r"[^ \t\r\n]+" => (TokenType::Identifier { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), @@ -37,31 +36,25 @@ pub mod lexer { type Item = (TokenType, Span); fn next(&mut self) -> Option<(TokenType, Span)> { - loop { - let tok: (TokenType, &str) = - if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { - self.remaining = new_remaining; - if token_type.is_new_line() { - self.line += 1; - self.character = text.len(); - } else { - self.character += text.len(); - } - (token_type, text) + let tok: (TokenType, &str) = + if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { + self.remaining = new_remaining; + if token_type.is_new_line() { + self.line += 1; + self.character = text.len(); } else { - return None; - }; - match tok { - (tok, text) => { - let span = self.span_in(text); - let token = tok.move_to( - self.line.clone(), - self.character - text.len(), - span.lo.clone(), - span.hi.clone(), - ); - return Some((token, span)); + self.character += text.len(); } + (token_type, text) + } else { + return None; + }; + match tok { + (tok, text) => { + let span = self.span_in(text); + let token = + tok.move_to(self.line, self.character - text.len(), span.lo, span.hi); + Some((token, span)) } } } @@ -80,8 +73,8 @@ pub mod lexer { #[cfg(test)] mod tests { - use crate::lexer::plain::*; - use crate::lexer::*; + use super::*; + use crate::{Token, TokenType}; #[test] fn must_parse_simple_text() { diff --git a/src/lexer/rust_lang.rs b/rider-lexers/src/rust_lang.rs similarity index 91% rename from src/lexer/rust_lang.rs rename to rider-lexers/src/rust_lang.rs index c0551fb..704d9db 100644 --- a/src/lexer/rust_lang.rs +++ b/rider-lexers/src/rust_lang.rs @@ -1,7 +1,5 @@ -use crate::lexer::{Token, TokenType}; - pub mod lexer { - use crate::lexer::{Span, Token, TokenType}; + use crate::{Span, Token, TokenType}; use plex::lexer; lexer! { @@ -11,6 +9,10 @@ pub mod lexer { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), + "(r\"|\")" => (TokenType::String { + token: Token::new(text.to_string(), 0, 0, 0, 0) + }, text), + r"([0-9]+|[0-9]+\.[0-9]+|'[^']')" => (TokenType::Literal { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), @@ -30,6 +32,10 @@ pub mod lexer { r"[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), + + r"'[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier { + token: Token::new(text.to_string(), 0, 0, 0, 0) + }, text), } pub struct Lexer<'a> { @@ -54,31 +60,25 @@ pub mod lexer { type Item = (TokenType, Span); fn next(&mut self) -> Option<(TokenType, Span)> { - loop { - let tok: (TokenType, &str) = - if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { - self.remaining = new_remaining; - if token_type.is_new_line() { - self.line += 1; - self.character = text.len(); - } else { - self.character += text.len(); - } - (token_type, text) + let tok: (TokenType, &str) = + if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { + self.remaining = new_remaining; + (token_type, text) + } else { + return None; + }; + match tok { + (tok, text) => { + let line = self.line; + if tok.is_new_line() { + self.line += 1; + self.character = text.len(); } else { - return None; - }; - match tok { - (tok, text) => { - let span = self.span_in(text); - let token = tok.move_to( - self.line.clone(), - self.character - text.len(), - span.lo.clone(), - span.hi.clone(), - ); - return Some((token, span)); + self.character += text.len(); } + let span = self.span_in(text); + let token = tok.move_to(line, self.character - text.len(), span.lo, span.hi); + Some((token, span)) } } } @@ -97,8 +97,8 @@ pub mod lexer { #[cfg(test)] mod tests { - use crate::lexer::rust_lang::*; - use crate::lexer::*; + use super::*; + use crate::{Token, TokenType}; #[test] fn must_parse_simple_text() { @@ -207,7 +207,7 @@ mod tests { token: Token::new("foo".to_string(), 0, 0, 0, 3), }, TokenType::Whitespace { - token: Token::new("\n".to_string(), 1, 0, 3, 4), + token: Token::new("\n".to_string(), 0, 0, 3, 4), }, TokenType::Identifier { token: Token::new("bar".to_string(), 1, 1, 4, 7), @@ -402,7 +402,7 @@ mod tests { token: Token::new("{".to_string(), 0, 30, 30, 31), }, TokenType::Whitespace { - token: Token::new("\n".to_string(), 1, 0, 31, 32), + token: Token::new("\n".to_string(), 0, 0, 31, 32), }, TokenType::Whitespace { token: Token::new(" ".to_string(), 1, 1, 32, 44), @@ -423,7 +423,7 @@ mod tests { token: Token::new("b".to_string(), 1, 17, 48, 49), }, TokenType::Whitespace { - token: Token::new("\n".to_string(), 2, 0, 49, 50), + token: Token::new("\n".to_string(), 1, 0, 49, 50), }, TokenType::Whitespace { token: Token::new(" ".to_string(), 2, 1, 50, 58), diff --git a/rider-themes/Cargo.toml b/rider-themes/Cargo.toml new file mode 100644 index 0000000..38e8680 --- /dev/null +++ b/rider-themes/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rider-themes" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +dirs = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" + +[dependencies.sdl2] +version = "0.31.0" +features = ["gfx", "image", "mixer", "ttf"] diff --git a/src/themes/caret_color.rs b/rider-themes/src/caret_color.rs similarity index 79% rename from src/themes/caret_color.rs rename to rider-themes/src/caret_color.rs index a2965ed..a1f4b3c 100644 --- a/src/themes/caret_color.rs +++ b/rider-themes/src/caret_color.rs @@ -1,5 +1,5 @@ -use crate::themes::SerdeColor; -use crate::themes::ThemeConfig; +use crate::SerdeColor; +use crate::ThemeConfig; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct CaretColor { @@ -10,7 +10,7 @@ pub struct CaretColor { impl Default for CaretColor { fn default() -> Self { Self { - bright: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + bright: ThemeConfig::new(SerdeColor::new(120, 120, 120, 0), false, false), blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), } } diff --git a/rider-themes/src/code_highlighting_color.rs b/rider-themes/src/code_highlighting_color.rs new file mode 100644 index 0000000..2ed1761 --- /dev/null +++ b/rider-themes/src/code_highlighting_color.rs @@ -0,0 +1,273 @@ +use crate::SerdeColor; +use crate::ThemeConfig; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct CodeHighlightingColor { + pub comment: ThemeConfig, + pub constant: ThemeConfig, + pub error: ThemeConfig, + pub warning: ThemeConfig, + pub identifier: ThemeConfig, + pub keyword: ThemeConfig, + pub literal: ThemeConfig, + pub number: ThemeConfig, + pub operator: ThemeConfig, + pub separator: ThemeConfig, + pub statement: ThemeConfig, + pub string: ThemeConfig, + pub title: ThemeConfig, + pub type_: ThemeConfig, + pub todo: ThemeConfig, + pub pre_proc: ThemeConfig, + pub special: ThemeConfig, + pub whitespace: ThemeConfig, +} + +impl Default for CodeHighlightingColor { + fn default() -> Self { + Self { + comment: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + constant: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + error: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + warning: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + identifier: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + keyword: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + literal: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + number: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + operator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + separator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + statement: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + string: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + title: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + type_: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + todo: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + pre_proc: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + special: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + whitespace: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + } + } +} + +impl CodeHighlightingColor { + pub fn comment(&self) -> &ThemeConfig { + &self.comment + } + + pub fn constant(&self) -> &ThemeConfig { + &self.constant + } + + pub fn error(&self) -> &ThemeConfig { + &self.error + } + + pub fn warning(&self) -> &ThemeConfig { + &self.warning + } + + pub fn identifier(&self) -> &ThemeConfig { + &self.identifier + } + + pub fn keyword(&self) -> &ThemeConfig { + &self.keyword + } + + pub fn literal(&self) -> &ThemeConfig { + &self.literal + } + + pub fn number(&self) -> &ThemeConfig { + &self.number + } + + pub fn operator(&self) -> &ThemeConfig { + &self.operator + } + + pub fn separator(&self) -> &ThemeConfig { + &self.separator + } + + pub fn statement(&self) -> &ThemeConfig { + &self.statement + } + + pub fn string(&self) -> &ThemeConfig { + &self.string + } + + pub fn title(&self) -> &ThemeConfig { + &self.title + } + + pub fn type_(&self) -> &ThemeConfig { + &self.type_ + } + + pub fn todo(&self) -> &ThemeConfig { + &self.todo + } + + pub fn pre_proc(&self) -> &ThemeConfig { + &self.pre_proc + } + + pub fn special(&self) -> &ThemeConfig { + &self.special + } + + pub fn whitespace(&self) -> &ThemeConfig { + &self.whitespace + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_comment() { + let target = CodeHighlightingColor::default(); + let result = target.comment().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_constant() { + let target = CodeHighlightingColor::default(); + let result = target.constant().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_error() { + let target = CodeHighlightingColor::default(); + let result = target.error().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_warning() { + let target = CodeHighlightingColor::default(); + let result = target.warning().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_identifier() { + let target = CodeHighlightingColor::default(); + let result = target.identifier().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_keyword() { + let target = CodeHighlightingColor::default(); + let result = target.keyword().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_literal() { + let target = CodeHighlightingColor::default(); + let result = target.literal().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_number() { + let target = CodeHighlightingColor::default(); + let result = target.number().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_operator() { + let target = CodeHighlightingColor::default(); + let result = target.operator().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_separator() { + let target = CodeHighlightingColor::default(); + let result = target.separator().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_statement() { + let target = CodeHighlightingColor::default(); + let result = target.statement().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_string() { + let target = CodeHighlightingColor::default(); + let result = target.string().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_title() { + let target = CodeHighlightingColor::default(); + let result = target.title().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_type_() { + let target = CodeHighlightingColor::default(); + let result = target.type_().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_todo() { + let target = CodeHighlightingColor::default(); + let result = target.todo().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_pre_proc() { + let target = CodeHighlightingColor::default(); + let result = target.pre_proc().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_special() { + let target = CodeHighlightingColor::default(); + let result = target.special().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + + #[test] + fn assert_whitespace() { + let target = CodeHighlightingColor::default(); + let result = target.whitespace().clone(); + let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false); + assert_eq!(result, expected); + } + +} diff --git a/src/themes/diff_color.rs b/rider-themes/src/diff_color.rs similarity index 67% rename from src/themes/diff_color.rs rename to rider-themes/src/diff_color.rs index 398df64..ac7e586 100644 --- a/src/themes/diff_color.rs +++ b/rider-themes/src/diff_color.rs @@ -1,5 +1,5 @@ -use crate::themes::SerdeColor; -use crate::themes::ThemeConfig; +use crate::SerdeColor; +use crate::ThemeConfig; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct DiffColor { @@ -12,9 +12,9 @@ pub struct DiffColor { impl Default for DiffColor { fn default() -> Self { Self { - add: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - delete: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - change: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + add: ThemeConfig::new(SerdeColor::new(0, 200, 0, 0), false, false), + delete: ThemeConfig::new(SerdeColor::new(200, 0, 0, 0), false, false), + change: ThemeConfig::new(SerdeColor::new(0, 0, 200, 0), false, false), text: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), } } diff --git a/rider-themes/src/images.rs b/rider-themes/src/images.rs new file mode 100644 index 0000000..ad9e833 --- /dev/null +++ b/rider-themes/src/images.rs @@ -0,0 +1,51 @@ +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct ThemeImages { + directory_icon: String, + file_icon: String, +} + +impl ThemeImages { + pub fn new(directory_icon: String, file_icon: String) -> Self { + Self { + file_icon, + directory_icon, + } + } + + pub fn directory_icon(&self) -> String { + self.directory_icon.clone() + } + + pub fn file_icon(&self) -> String { + self.file_icon.clone() + } +} + +impl Default for ThemeImages { + fn default() -> Self { + Self { + directory_icon: "default/images/directory-64x64.png".to_string(), + file_icon: "default/images/file-64x64.png".to_string(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn assert_directory_icon() { + let config = ThemeImages::new("foo".to_owned(), "bar".to_owned()); + let result = config.directory_icon(); + let expected = "foo".to_owned(); + assert_eq!(result, expected); + } + #[test] + fn assert_file_icon() { + let config = ThemeImages::new("foo".to_owned(), "bar".to_owned()); + let result = config.file_icon(); + let expected = "bar".to_owned(); + assert_eq!(result, expected); + } +} diff --git a/rider-themes/src/lib.rs b/rider-themes/src/lib.rs new file mode 100644 index 0000000..3f47f00 --- /dev/null +++ b/rider-themes/src/lib.rs @@ -0,0 +1,21 @@ +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; + +pub mod caret_color; +pub mod code_highlighting_color; +pub mod diff_color; +pub mod images; +pub mod predef; +pub mod serde_color; +pub mod theme; +pub mod theme_config; + +pub use crate::caret_color::CaretColor; +pub use crate::code_highlighting_color::CodeHighlightingColor; +pub use crate::diff_color::DiffColor; +pub use crate::images::ThemeImages; +pub use crate::serde_color::SerdeColor; +pub use crate::theme::Theme; +pub use crate::theme_config::ThemeConfig; diff --git a/src/themes/predef/default.rs b/rider-themes/src/predef/default.rs similarity index 68% rename from src/themes/predef/default.rs rename to rider-themes/src/predef/default.rs index 14e6bb2..b9f537e 100644 --- a/src/themes/predef/default.rs +++ b/rider-themes/src/predef/default.rs @@ -1,4 +1,4 @@ -use crate::themes::Theme; +use crate::Theme; pub fn build_theme() -> Theme { Theme::default() diff --git a/src/themes/predef/mod.rs b/rider-themes/src/predef/mod.rs similarity index 100% rename from src/themes/predef/mod.rs rename to rider-themes/src/predef/mod.rs diff --git a/src/themes/predef/railscasts.rs b/rider-themes/src/predef/railscasts.rs similarity index 85% rename from src/themes/predef/railscasts.rs rename to rider-themes/src/predef/railscasts.rs index a89f5f9..29b6c07 100644 --- a/src/themes/predef/railscasts.rs +++ b/rider-themes/src/predef/railscasts.rs @@ -1,14 +1,16 @@ -use crate::themes::caret_color::CaretColor; -use crate::themes::CodeHighlightingColor; -use crate::themes::DiffColor; -use crate::themes::SerdeColor; -use crate::themes::Theme; -use crate::themes::ThemeConfig; +use crate::caret_color::CaretColor; +use crate::CodeHighlightingColor; +use crate::DiffColor; +use crate::SerdeColor; +use crate::Theme; +use crate::ThemeConfig; +use crate::ThemeImages; pub fn build_theme() -> Theme { Theme::new( "railscasts".to_string(), SerdeColor::new(18, 18, 18, 0), + SerdeColor::new(200, 200, 200, 0), CaretColor::new( ThemeConfig::new(SerdeColor::new(121, 121, 121, 0), false, false), ThemeConfig::new(SerdeColor::new(21, 21, 21, 0), false, false), @@ -39,5 +41,9 @@ pub fn build_theme() -> Theme { ThemeConfig::new(SerdeColor::new(135, 0, 135, 0), false, false), ThemeConfig::new(SerdeColor::new(18, 18, 18, 0), false, false), ), + ThemeImages::new( + "railscasts/images/directory-64x64.png".to_owned(), + "railscasts/images/file-64x64.png".to_owned(), + ), ) } diff --git a/rider-themes/src/serde_color.rs b/rider-themes/src/serde_color.rs new file mode 100644 index 0000000..7daabaa --- /dev/null +++ b/rider-themes/src/serde_color.rs @@ -0,0 +1,49 @@ +use sdl2::pixels::Color; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct SerdeColor { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl SerdeColor { + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } +} + +impl Into for &SerdeColor { + fn into(self) -> Color { + Color { + r: self.r, + g: self.g, + b: self.b, + a: self.a, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use sdl2::pixels::Color; + + #[test] + fn must_cast_serde_color_to_color() { + let target = SerdeColor::new(12, 34, 56, 78); + let color: Color = (&target).into(); + let expected = Color::RGBA(12, 34, 56, 78); + assert_eq!(color, expected); + } + + #[test] + fn must_assign_to_proper_fields() { + let color = SerdeColor::new(12, 34, 56, 78); + assert_eq!(color.r, 12); + assert_eq!(color.g, 34); + assert_eq!(color.b, 56); + assert_eq!(color.a, 78); + } +} diff --git a/rider-themes/src/theme.rs b/rider-themes/src/theme.rs new file mode 100644 index 0000000..80c3ab6 --- /dev/null +++ b/rider-themes/src/theme.rs @@ -0,0 +1,141 @@ +use crate::CaretColor; +use crate::CodeHighlightingColor; +use crate::DiffColor; +use crate::SerdeColor; +use crate::ThemeImages; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct Theme { + name: String, + background: SerdeColor, + border_color: SerdeColor, + caret: CaretColor, + code_highlighting: CodeHighlightingColor, + diff: DiffColor, + images: ThemeImages, +} + +impl Default for Theme { + fn default() -> Self { + Self { + name: "default".to_string(), + background: SerdeColor::new(255, 255, 255, 0), + border_color: SerdeColor::new(0, 0, 0, 0), + caret: CaretColor::default(), + code_highlighting: CodeHighlightingColor::default(), + diff: DiffColor::default(), + images: ThemeImages::default(), + } + } +} + +impl Theme { + pub fn new( + name: String, + background: SerdeColor, + border_color: SerdeColor, + caret: CaretColor, + code_highlighting: CodeHighlightingColor, + diff: DiffColor, + images: ThemeImages, + ) -> Self { + Self { + name, + background, + border_color, + caret, + code_highlighting, + diff, + images, + } + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn background(&self) -> &SerdeColor { + &self.background + } + + pub fn border_color(&self) -> &SerdeColor { + &self.border_color + } + + pub fn caret(&self) -> &CaretColor { + &self.caret + } + + pub fn diff(&self) -> &DiffColor { + &self.diff + } + + pub fn code_highlighting(&self) -> &CodeHighlightingColor { + &self.code_highlighting + } + + pub fn images(&self) -> &ThemeImages { + &self.images + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_name() { + let target = Theme::default(); + let result = target.name().clone(); + let expected = "default".to_owned(); + assert_eq!(result, expected); + } + + #[test] + fn assert_background() { + let target = Theme::default(); + let result = target.background().clone(); + let expected = SerdeColor::new(255, 255, 255, 0); + assert_eq!(result, expected); + } + + #[test] + fn assert_border_color() { + let target = Theme::default(); + let result = target.border_color().clone(); + let expected = SerdeColor::new(0, 0, 0, 0); + assert_eq!(result, expected); + } + + #[test] + fn assert_caret() { + let target = Theme::default(); + let result = target.caret().clone(); + let expected = CaretColor::default(); + assert_eq!(result, expected); + } + + #[test] + fn assert_diff() { + let target = Theme::default(); + let result = target.diff().clone(); + let expected = DiffColor::default(); + assert_eq!(result, expected); + } + + #[test] + fn assert_code_highlighting() { + let target = Theme::default(); + let result = target.code_highlighting().clone(); + let expected = CodeHighlightingColor::default(); + assert_eq!(result, expected); + } + + #[test] + fn assert_images() { + let target = Theme::default(); + let result = target.images().clone(); + let expected = ThemeImages::default(); + assert_eq!(result, expected); + } +} diff --git a/rider-themes/src/theme_config.rs b/rider-themes/src/theme_config.rs new file mode 100644 index 0000000..e1d03bc --- /dev/null +++ b/rider-themes/src/theme_config.rs @@ -0,0 +1,59 @@ +use crate::SerdeColor; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct ThemeConfig { + color: SerdeColor, + italic: bool, + bold: bool, +} + +impl ThemeConfig { + pub fn new(color: SerdeColor, italic: bool, bold: bool) -> Self { + Self { + color, + italic, + bold, + } + } + + pub fn color(&self) -> &SerdeColor { + &self.color + } + + pub fn italic(&self) -> bool { + self.italic + } + + pub fn bold(&self) -> bool { + self.bold + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_color() { + let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), true, false); + let result = target.color().clone(); + let expected = SerdeColor::new(29, 20, 45, 72); + assert_eq!(result, expected); + } + + #[test] + fn assert_italic() { + let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), true, false); + let result = target.italic(); + let expected = true; + assert_eq!(result, expected); + } + + #[test] + fn assert_bold() { + let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), false, true); + let result = target.bold(); + let expected = true; + assert_eq!(result, expected); + } +} diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..ee22031 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +cargo test -p rider-generator +cargo test -p rider-config +cargo test -p rider-themes +cargo test -p rider-lexers +cargo test -p rider-editor diff --git a/src/app/app_state.rs b/src/app/app_state.rs deleted file mode 100644 index dc0b98d..0000000 --- a/src/app/app_state.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::app::caret_manager; -use crate::app::file_content_manager; -use crate::app::{UpdateResult, WindowCanvas as WC}; -use crate::config::*; -use crate::renderer::Renderer; -use crate::ui::caret::*; -use crate::ui::file::editor_file::EditorFile; -use crate::ui::file::*; -use crate::ui::menu_bar::MenuBar; -use crate::ui::text_character::TextCharacter; -use crate::ui::*; -use sdl2::rect::{Point, Rect}; -use sdl2::VideoSubsystem as VS; -use std::boxed::Box; -use std::rc::Rc; -use std::sync::*; - -pub struct AppState { - menu_bar: MenuBar, - files: Vec, - config: Arc>, - file_editor: FileEditor, -} - -impl AppState { - pub fn new(config: Arc>) -> Self { - Self { - menu_bar: MenuBar::new(Arc::clone(&config)), - files: vec![], - file_editor: FileEditor::new(Arc::clone(&config)), - config, - } - } - - pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) { - use std::fs::read_to_string; - - if let Ok(buffer) = read_to_string(&file_path) { - let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); - file.prepare_ui(renderer); - match self.file_editor.open_file(file) { - Some(old) => self.files.push(old), - _ => (), - } - } else { - eprintln!("Failed to open file: {}", file_path); - }; - } - - pub fn file_editor(&self) -> &FileEditor { - &self.file_editor - } - - pub fn file_editor_mut(&mut self) -> &mut FileEditor { - &mut self.file_editor - } -} - -impl Render for AppState { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) { - self.file_editor - .render(canvas, renderer, &RenderContext::Nothing); - self.menu_bar - .render(canvas, renderer, &RenderContext::Nothing); - } - - fn prepare_ui(&mut self, renderer: &mut Renderer) { - self.menu_bar.prepare_ui(renderer); - self.file_editor.prepare_ui(renderer); - } -} - -impl Update for AppState { - fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { - self.menu_bar.update(ticks, context); - self.file_editor.update(ticks, context); - UpdateResult::NoOp - } -} - -impl AppState { - pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult { - if self - .menu_bar - .is_left_click_target(point, &UpdateContext::Nothing) - { - video_subsystem.text_input().stop(); - return self.menu_bar.on_left_click(point, &UpdateContext::Nothing); - } else { - if !self - .file_editor - .is_left_click_target(point, &UpdateContext::Nothing) - { - return UpdateResult::NoOp; - } else { - video_subsystem.text_input().start(); - self.file_editor - .on_left_click(point, &UpdateContext::Nothing); - } - } - UpdateResult::NoOp - } - - pub fn is_left_click_target(&self, _point: &Point) -> bool { - true - } -} - -impl ConfigHolder for AppState { - fn config(&self) -> &ConfigAccess { - &self.config - } -} diff --git a/src/app/application.rs b/src/app/application.rs deleted file mode 100644 index 246e96a..0000000 --- a/src/app/application.rs +++ /dev/null @@ -1,260 +0,0 @@ -pub use crate::app::app_state::AppState; -pub use crate::config::{Config, ConfigAccess, ConfigHolder}; -pub use crate::renderer::Renderer; -use crate::themes::*; -use crate::ui::caret::{CaretPosition, MoveDirection}; -use crate::ui::*; - -use std::rc::Rc; -use std::sync::*; -use std::thread::sleep; -use std::time::Duration; - -use sdl2::event::*; -use sdl2::hint; -use sdl2::keyboard::{Keycode, Mod}; -use sdl2::mouse::*; -use sdl2::pixels::{Color, PixelFormatEnum}; -use sdl2::rect::{Point, Rect}; -use sdl2::render::Canvas; -use sdl2::rwops::RWops; -use sdl2::surface::Surface; -use sdl2::ttf::Sdl2TtfContext; -use sdl2::video::Window; -use sdl2::EventPump; -use sdl2::{Sdl, TimerSubsystem, VideoSubsystem}; - -pub type WindowCanvas = Canvas; - -#[derive(PartialEq, Clone, Debug)] -pub enum UpdateResult { - NoOp, - Stop, - RefreshPositions, - MouseLeftClicked(Point), - MoveCaret(Rect, CaretPosition), - DeleteFront, - DeleteBack, - Input(String), - InsertNewLine, - MoveCaretLeft, - MoveCaretRight, - MoveCaretUp, - MoveCaretDown, - Scroll { x: i32, y: i32 }, - WindowResize { width: i32, height: i32 }, -} - -pub enum Task { - OpenFile { file_path: String }, -} - -pub struct Application { - config: Arc>, - clear_color: Color, - sdl_context: Sdl, - canvas: WindowCanvas, - video_subsystem: VideoSubsystem, - tasks: Vec, -} - -impl Application { - pub fn new() -> Self { - let config = Arc::new(RwLock::new(Config::new())); - let sdl_context = sdl2::init().unwrap(); - - hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1"); - hint::set("SDL_GL_MULTISAMPLESAMPLES", "8"); - hint::set("SDL_GL_ACCELERATED_VISUAL", "1"); - hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2"); - hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1"); - - let video_subsystem = sdl_context.video().unwrap(); - - let mut window: Window = { - let c = config.read().unwrap(); - video_subsystem - .window("Rider", c.width(), c.height()) - .position_centered() - .resizable() - .opengl() - .build() - .unwrap() - }; - let icon_bytes = include_bytes!("../../assets/gear-64x64.bmp").clone(); - let mut rw = RWops::from_bytes(&icon_bytes).unwrap(); - let mut icon = Surface::load_bmp_rw(&mut rw).unwrap(); - window.set_icon(&mut icon); - - let canvas = window.into_canvas().accelerated().build().unwrap(); - let clear_color: Color = { config.read().unwrap().theme().background().into() }; - - Self { - sdl_context, - video_subsystem, - canvas, - tasks: vec![], - clear_color, - config, - } - } - - pub fn init(&mut self) { - self.clear(); - } - - pub fn run(&mut self) { - let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap(); - let mut event_pump = self.sdl_context.event_pump().unwrap(); - let font_context = sdl2::ttf::init().unwrap(); - let texture_creator = self.canvas.texture_creator(); - let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); - let mut app_state = AppState::new(Arc::clone(&self.config)); - let mut renderer = Renderer::new(Arc::clone(&self.config), &font_context, &texture_creator); - app_state.prepare_ui(&mut renderer); - - 'running: loop { - match self.handle_events(&mut event_pump) { - UpdateResult::Stop => break 'running, - UpdateResult::RefreshPositions => (), - UpdateResult::NoOp => (), - UpdateResult::MoveCaret(_, _pos) => (), - UpdateResult::MouseLeftClicked(point) => { - app_state.on_left_click(&point, &mut self.video_subsystem); - } - UpdateResult::DeleteFront => { - app_state.file_editor_mut().delete_front(&mut renderer); - } - UpdateResult::DeleteBack => { - app_state.file_editor_mut().delete_back(&mut renderer); - } - UpdateResult::Input(text) => { - app_state.file_editor_mut().insert_text(text, &mut renderer); - } - UpdateResult::InsertNewLine => { - app_state.file_editor_mut().insert_new_line(&mut renderer); - } - UpdateResult::MoveCaretLeft => { - app_state.file_editor_mut().move_caret(MoveDirection::Left); - } - UpdateResult::MoveCaretRight => { - app_state.file_editor_mut().move_caret(MoveDirection::Right); - } - UpdateResult::MoveCaretUp => { - app_state.file_editor_mut().move_caret(MoveDirection::Up); - } - UpdateResult::MoveCaretDown => { - app_state.file_editor_mut().move_caret(MoveDirection::Down); - } - UpdateResult::Scroll { x, y } => { - app_state.file_editor_mut().scroll_to(-x, -y); - } - UpdateResult::WindowResize { width, height } => { - let mut c = app_state.config().write().unwrap(); - if width > 0 { - c.set_width(width as u32); - } - if height > 0 { - c.set_height(height as u32); - } - } - } - for task in self.tasks.iter() { - match task { - Task::OpenFile { file_path } => { - use crate::ui::file::editor_file::*; - app_state.open_file(file_path.clone(), &mut renderer); - } - } - } - self.tasks.clear(); - - self.clear(); - - app_state.update(timer.ticks() as i32, &UpdateContext::Nothing); - app_state.render(&mut self.canvas, &mut renderer, &RenderContext::Nothing); - - self.present(); - sleep(sleep_time); - } - } - - pub fn open_file(&mut self, file_path: String) { - self.tasks.push(Task::OpenFile { file_path }); - } - - fn present(&mut self) { - self.canvas.present(); - } - - fn clear(&mut self) { - self.canvas.set_draw_color(self.clear_color.clone()); - self.canvas.clear(); - } - - fn handle_events(&mut self, event_pump: &mut EventPump) -> UpdateResult { - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } => return UpdateResult::Stop, - Event::MouseButtonUp { - mouse_btn, x, y, .. - } => match mouse_btn { - MouseButton::Left => return UpdateResult::MouseLeftClicked(Point::new(x, y)), - _ => (), - }, - Event::KeyDown { keycode, .. } => { - let keycode = if keycode.is_some() { - keycode.unwrap() - } else { - return UpdateResult::NoOp; - }; - match keycode { - Keycode::Backspace => return UpdateResult::DeleteFront, - Keycode::Delete => return UpdateResult::DeleteBack, - Keycode::KpEnter | Keycode::Return => return UpdateResult::InsertNewLine, - Keycode::Left => return UpdateResult::MoveCaretLeft, - Keycode::Right => return UpdateResult::MoveCaretRight, - Keycode::Up => return UpdateResult::MoveCaretUp, - Keycode::Down => return UpdateResult::MoveCaretDown, - _ => UpdateResult::NoOp, - }; - } - Event::TextInput { text, .. } => { - return UpdateResult::Input(text); - } - Event::MouseWheel { - direction, x, y, .. - } => { - match direction { - MouseWheelDirection::Normal => { - return UpdateResult::Scroll { x, y }; - } - MouseWheelDirection::Flipped => { - return UpdateResult::Scroll { x, y: -y }; - } - _ => { - // ignore - } - }; - } - Event::Window { - win_event: WindowEvent::Resized(w, h), - .. - } => { - return UpdateResult::WindowResize { - width: w, - height: h, - }; - } - _ => (), - } - } - UpdateResult::NoOp - } -} - -impl ConfigHolder for Application { - fn config(&self) -> &ConfigAccess { - &self.config - } -} diff --git a/src/app/caret_manager.rs b/src/app/caret_manager.rs deleted file mode 100644 index 2058ae7..0000000 --- a/src/app/caret_manager.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::app::AppState; -use crate::ui::*; -use sdl2::rect::{Point, Rect}; - -pub fn move_caret_right(file_editor: &mut FileEditor) { - let file: &EditorFile = match file_editor.file() { - None => return, - Some(f) => f, - }; - let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) { - Some(text_character) => text_character, - None => return, // EOF - }; - let caret_rect = file_editor.caret().dest().clone(); - let pos = file_editor.caret().position(); - let (d, p): (Rect, CaretPosition) = match ( - c.is_last_in_line(), - c.is_new_line(), - c.dest().y() == caret_rect.y(), - ) { - (true, true, false) => { - let prev: TextCharacter = if c.position() != 0 { - file.get_character_at(c.position() - 1).unwrap_or(c.clone()) - } else { - c.clone() - }; - let mut dest = prev.dest().clone(); - dest.set_x(dest.x() + dest.width() as i32); - (dest, pos.moved(1, 0, 0)) - } - (false, true, false) => { - let prev: TextCharacter = if c.position() != 0 { - file.get_character_at(c.position() - 1).unwrap_or(c.clone()) - } else { - c.clone() - }; - let mut dest = prev.dest().clone(); - if !prev.is_new_line() { - dest.set_x(dest.x() + dest.width() as i32); - } - (dest, pos.moved(1, 0, 0)) - } - (true, false, false) => { - // move after character, stay on current line - (c.dest().clone(), pos.moved(1, 0, 0)) - } - (true, false, true) => { - // move to new line - (c.dest().clone(), pos.moved(1, 0, 0)) - } - _ => (c.dest().clone(), pos.moved(1, 0, 0)), - }; - file_editor - .caret_mut() - .move_caret(p, Point::new(d.x(), d.y())); -} - -pub fn move_caret_left(file_editor: &mut FileEditor) { - let _file: &EditorFile = match file_editor.file() { - None => return, - Some(f) => f, - }; - let _line = file_editor.caret().line_number(); -} diff --git a/src/config/config.rs b/src/config/config.rs deleted file mode 100644 index 3c11652..0000000 --- a/src/config/config.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::config::creator; -use crate::config::EditorConfig; -use crate::config::ScrollConfig; -use crate::lexer::Language; -use crate::themes::Theme; -use dirs; -use std::collections::HashMap; -use std::fs; - -pub type LanguageMapping = HashMap; - -#[derive(Debug, Clone)] -pub struct Config { - width: u32, - height: u32, - menu_height: u16, - editor_config: EditorConfig, - theme: Theme, - extensions_mapping: LanguageMapping, - scroll: ScrollConfig, -} - -impl Config { - pub fn new() -> Self { - creator::create(); - let editor_config = EditorConfig::new(); - let mut extensions_mapping = HashMap::new(); - extensions_mapping.insert(".".to_string(), Language::PlainText); - extensions_mapping.insert("txt".to_string(), Language::PlainText); - extensions_mapping.insert("rs".to_string(), Language::Rust); - - Self { - width: 1024, - height: 860, - menu_height: 60, - theme: Theme::load(editor_config.current_theme().clone()), - editor_config, - extensions_mapping, - scroll: ScrollConfig::new(), - } - } - - pub fn width(&self) -> u32 { - self.width - } - - pub fn set_width(&mut self, w: u32) { - self.width = w; - } - - pub fn height(&self) -> u32 { - self.height - } - - pub fn set_height(&mut self, h: u32) { - self.height = h; - } - - pub fn editor_config(&self) -> &EditorConfig { - &self.editor_config - } - - pub fn theme(&self) -> &Theme { - &self.theme - } - - pub fn menu_height(&self) -> u16 { - self.menu_height - } - - pub fn editor_top_margin(&self) -> i32 { - (self.menu_height() as i32) + (self.editor_config().margin_top() as i32) - } - - pub fn editor_left_margin(&self) -> i32 { - self.editor_config().margin_left() as i32 - } - - pub fn extensions_mapping(&self) -> &LanguageMapping { - &self.extensions_mapping - } - - pub fn scroll(&self) -> &ScrollConfig { - &self.scroll - } - - pub fn scroll_mut(&mut self) -> &mut ScrollConfig { - &mut self.scroll - } -} - -#[cfg(test)] -mod tests { - use crate::config::*; - use crate::lexer::*; - - #[test] - fn must_return_language_mapping() { - let config = Config::new(); - - let mapping = config.extensions_mapping(); - { - let mut keys: Vec = mapping.keys().map(|s| s.to_string()).collect(); - let mut expected: Vec = - vec![".".to_string(), "txt".to_string(), "rs".to_string()]; - keys.sort(); - expected.sort(); - assert_eq!(keys, expected); - } - { - let mut keys: Vec = mapping.values().map(|s| s.clone()).collect(); - let mut expected: Vec = - vec![Language::PlainText, Language::PlainText, Language::Rust]; - keys.sort(); - expected.sort(); - assert_eq!(keys, expected); - } - } - - #[test] - fn assert_scroll() { - let config = Config::new(); - let result = config.scroll(); - let expected = ScrollConfig::new(); - assert_eq!(result.clone(), expected); - } - - #[test] - fn assert_scroll_mut() { - let mut config = Config::new(); - let result = config.scroll_mut(); - result.set_margin_right(1236); - let mut expected = ScrollConfig::new(); - expected.set_margin_right(1236); - assert_eq!(result.clone(), expected); - } - -} diff --git a/src/config/creator.rs b/src/config/creator.rs deleted file mode 100644 index 8a3db7b..0000000 --- a/src/config/creator.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::config::directories::*; -use crate::themes::config_creator; -use dirs; -use std::fs; -use std::path; - -pub fn create() { - if !themes_dir().exists() { - let r = fs::create_dir_all(&themes_dir()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot create themes config directory")); - } - - if !fonts_dir().exists() { - let r = fs::create_dir_all(&fonts_dir()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot create fonts config directory")); - write_default_fonts(); - } - - if !log_dir().exists() { - let r = fs::create_dir_all(&log_dir()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot create log directory")); - } - - if !project_dir().exists() { - let r = fs::create_dir_all(&project_dir()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot create project directory")); - } -} - -fn write_default_fonts() { - { - let mut default_font_path = fonts_dir(); - default_font_path.push("DejaVuSansMono.ttf"); - let contents = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf"); - let r = fs::write(default_font_path, contents.to_vec()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot write default font file!")); - } - { - let mut default_font_path = fonts_dir(); - default_font_path.push("ElaineSans-Medium.ttf"); - let contents = include_bytes!("../../assets/fonts/ElaineSans-Medium.ttf"); - let r = fs::write(default_font_path, contents.to_vec()); - #[cfg_attr(tarpaulin, skip)] - r.unwrap_or_else(|_| panic!("Cannot write default font file!")); - } -} diff --git a/src/config/directories.rs b/src/config/directories.rs deleted file mode 100644 index bf39ab2..0000000 --- a/src/config/directories.rs +++ /dev/null @@ -1,36 +0,0 @@ -use dirs; -use std::path::PathBuf; - -pub fn log_dir() -> PathBuf { - let mut log_dir = config_dir(); - log_dir.push("log"); - log_dir -} - -pub fn themes_dir() -> PathBuf { - let mut themes_dir = config_dir(); - themes_dir.push("themes"); - themes_dir -} - -pub fn fonts_dir() -> PathBuf { - let mut fonts_dir = config_dir(); - fonts_dir.push("fonts"); - fonts_dir -} - -pub fn config_dir() -> PathBuf { - let home_dir = dirs::config_dir().unwrap(); - - let mut config_dir = home_dir.clone(); - config_dir.push("rider"); - config_dir -} - -pub fn project_dir() -> PathBuf { - let runtime = dirs::runtime_dir().unwrap(); - - let mut project_dir = runtime.clone(); - project_dir.push(".rider"); - project_dir -} diff --git a/src/config/editor_config.rs b/src/config/editor_config.rs deleted file mode 100644 index cb6e6b9..0000000 --- a/src/config/editor_config.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::config::directories; - -#[derive(Debug, Clone)] -pub struct EditorConfig { - character_size: u16, - font_path: String, - current_theme: String, - margin_left: u16, - margin_top: u16, -} - -impl EditorConfig { - pub fn new() -> Self { - let mut default_font_path = directories::fonts_dir(); - default_font_path.push("DejaVuSansMono.ttf"); - Self { - character_size: 14, - font_path: default_font_path.to_str().unwrap().to_string(), - current_theme: "railscasts".to_string(), - margin_left: 10, - margin_top: 10, - } - } - - pub fn character_size(&self) -> u16 { - self.character_size - } - - pub fn font_path(&self) -> &String { - &self.font_path - } - - pub fn current_theme(&self) -> &String { - &self.current_theme - } - - pub fn margin_left(&self) -> u16 { - self.margin_left - } - - pub fn margin_top(&self) -> u16 { - self.margin_top - } -} diff --git a/src/main.rs b/src/main.rs index ca5ddb7..7f8ba6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,59 +1,53 @@ -#![allow(unused_imports)] - -extern crate dirs; -extern crate plex; -extern crate rand; -extern crate sdl2; -#[macro_use] -extern crate serde; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; -#[macro_use] -extern crate log; -extern crate simplelog; -#[macro_use] -extern crate lazy_static; - -use crate::app::Application; -use crate::config::directories::log_dir; -use log::Level; -use simplelog::*; -use std::fs::create_dir_all; -use std::fs::File; - -pub mod app; -pub mod config; -pub mod lexer; -pub mod renderer; -#[cfg(test)] -pub mod tests; -pub mod themes; -pub mod ui; - -fn init_logger() { - use simplelog::SharedLogger; - - let mut log_file_path = log_dir(); - log_file_path.push("rider.log"); - - let mut outputs: Vec> = vec![WriteLogger::new( - LevelFilter::Info, - Config::default(), - File::create(log_file_path).unwrap(), - )]; - if let Some(term) = TermLogger::new(LevelFilter::Warn, Config::default()) { - outputs.push(term); - } - - CombinedLogger::init(outputs).unwrap(); -} +extern crate rider_config; +use std::process::Command; fn main() { - let mut app = Application::new(); - app.init(); - init_logger(); - app.open_file("./assets/examples/test.rs".to_string()); - app.run(); + let generator = rider_config::directories::get_binary_path("rider-generator").unwrap(); + println!("generator will be {:?}", generator); + Command::new(generator).status().unwrap(); + + let editor = rider_config::directories::get_binary_path("rider-editor").unwrap(); + println!("editor will be {:?}", editor); + Command::new(editor).status().unwrap(); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env::set_var; + use std::fs::create_dir_all; + use std::path::Path; + use uuid::Uuid; + + #[cfg(test)] + fn exists(dir: &String, sub: &str) -> bool { + Path::new(join(dir.clone(), sub.to_owned()).as_str()).exists() + } + + #[cfg(test)] + fn join(a: String, b: String) -> String { + vec![a, b].join("/") + } + + #[test] + fn assert_main() { + let uniq = Uuid::new_v4(); + let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string()); + create_dir_all(test_path.clone()).unwrap(); + set_var("XDG_CONFIG_HOME", test_path.as_str()); + set_var("XDG_RUNTIME_DIR", test_path.as_str()); + let rider_dir = join(test_path.clone(), "rider".to_owned()); + assert_eq!(exists(&rider_dir, "themes"), false); + assert_eq!(exists(&rider_dir, "log"), false); + assert_eq!(exists(&test_path, ".rider"), false); + assert_eq!(exists(&rider_dir, "themes/default.json"), false); + assert_eq!(exists(&rider_dir, "themes/railscasts.json"), false); + main(); + assert_eq!(exists(&rider_dir, "fonts"), true); + assert_eq!(exists(&rider_dir, "log"), true); + assert_eq!(exists(&rider_dir, "themes"), true); + assert_eq!(exists(&test_path, ".rider"), true); + assert_eq!(exists(&rider_dir, "themes/default.json"), true); + assert_eq!(exists(&rider_dir, "themes/railscasts.json"), true); + } } diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs deleted file mode 100644 index a872539..0000000 --- a/src/renderer/renderer.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::app::WindowCanvas as WC; -use crate::config::{Config, ConfigAccess, ConfigHolder}; -use crate::renderer::managers::*; -use sdl2::rect::{Point, Rect}; -use sdl2::render::{Texture, TextureCreator}; -use sdl2::ttf::Sdl2TtfContext; -use sdl2::video::WindowContext as WinCtxt; -use std::rc::Rc; -use std::sync::*; - -pub struct Renderer<'l> { - config: ConfigAccess, - font_manager: FontManager<'l>, - texture_manager: TextureManager<'l, WinCtxt>, -} - -impl<'l> Renderer<'l> { - pub fn new( - config: ConfigAccess, - font_context: &'l Sdl2TtfContext, - texture_creator: &'l TextureCreator, - ) -> Self { - Self { - config, - font_manager: FontManager::new(&font_context), - texture_manager: TextureManager::new(&texture_creator), - } - } -} - -impl<'l> ManagersHolder<'l> for Renderer<'l> { - fn font_manager(&mut self) -> &mut FontManager<'l> { - &mut self.font_manager - } - - fn texture_manager(&mut self) -> &mut TextureManager<'l, WinCtxt> { - &mut self.texture_manager - } -} - -impl<'l> ConfigHolder for Renderer<'l> { - fn config(&self) -> &ConfigAccess { - &self.config - } -} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 04199f4..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,28 +0,0 @@ -#[cfg(test)] -pub mod support { - use crate::config::*; - use crate::renderer::*; - use sdl2::render::{Canvas, WindowCanvas}; - use sdl2::*; - use sdl2::{Sdl, TimerSubsystem, VideoSubsystem}; - use std::borrow::*; - use std::sync::*; - - pub fn build_config() -> Arc> { - Arc::new(RwLock::new(Config::new())) - } - - pub fn build_canvas() -> WindowCanvas { - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); - - let window = video_subsystem - .window("Test", 1, 1) - .borderless() - .opengl() - .build() - .unwrap(); - - window.into_canvas().accelerated().build().unwrap() - } -} diff --git a/src/themes/code_highlighting_color.rs b/src/themes/code_highlighting_color.rs deleted file mode 100644 index 4ae0def..0000000 --- a/src/themes/code_highlighting_color.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::themes::SerdeColor; -use crate::themes::ThemeConfig; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct CodeHighlightingColor { - pub comment: ThemeConfig, - pub constant: ThemeConfig, - pub error: ThemeConfig, - pub warning: ThemeConfig, - pub identifier: ThemeConfig, - pub keyword: ThemeConfig, - pub literal: ThemeConfig, - pub number: ThemeConfig, - pub operator: ThemeConfig, - pub separator: ThemeConfig, - pub statement: ThemeConfig, - pub string: ThemeConfig, - pub title: ThemeConfig, - pub type_: ThemeConfig, - pub todo: ThemeConfig, - pub pre_proc: ThemeConfig, - pub special: ThemeConfig, - pub whitespace: ThemeConfig, -} - -impl Default for CodeHighlightingColor { - fn default() -> Self { - Self { - comment: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - constant: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - error: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - warning: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - identifier: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - keyword: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - literal: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - number: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - operator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - separator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - statement: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - string: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - title: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - type_: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - todo: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - pre_proc: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - special: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - whitespace: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), - } - } -} - -impl CodeHighlightingColor { - pub fn comment(&self) -> &ThemeConfig { - &self.comment - } - - pub fn constant(&self) -> &ThemeConfig { - &self.constant - } - - pub fn error(&self) -> &ThemeConfig { - &self.error - } - - pub fn warning(&self) -> &ThemeConfig { - &self.warning - } - - pub fn identifier(&self) -> &ThemeConfig { - &self.identifier - } - - pub fn keyword(&self) -> &ThemeConfig { - &self.keyword - } - - pub fn literal(&self) -> &ThemeConfig { - &self.literal - } - - pub fn number(&self) -> &ThemeConfig { - &self.number - } - - pub fn operator(&self) -> &ThemeConfig { - &self.operator - } - - pub fn separator(&self) -> &ThemeConfig { - &self.separator - } - - pub fn statement(&self) -> &ThemeConfig { - &self.statement - } - - pub fn string(&self) -> &ThemeConfig { - &self.string - } - - pub fn title(&self) -> &ThemeConfig { - &self.title - } - - pub fn type_(&self) -> &ThemeConfig { - &self.type_ - } - - pub fn todo(&self) -> &ThemeConfig { - &self.todo - } - - pub fn pre_proc(&self) -> &ThemeConfig { - &self.pre_proc - } - - pub fn special(&self) -> &ThemeConfig { - &self.special - } - - pub fn whitespace(&self) -> &ThemeConfig { - &self.whitespace - } -} diff --git a/src/themes/config_creator.rs b/src/themes/config_creator.rs deleted file mode 100644 index 2f5c432..0000000 --- a/src/themes/config_creator.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::config::directories::*; -use crate::themes::predef::*; -use crate::themes::*; -use dirs; -use std::fs; -use std::path::PathBuf; - -pub fn create() { - fs::create_dir_all(themes_dir()) - .unwrap_or_else(|_| panic!("Cannot create theme config directory")); - for theme in default_styles() { - write_theme(&theme); - } -} - -fn write_theme(theme: &Theme) { - let mut theme_path = themes_dir(); - theme_path.push(format!("{}.json", theme.name())); - let contents = serde_json::to_string_pretty(&theme).unwrap(); - fs::write(&theme_path, contents.clone()) - .unwrap_or_else(|_| panic!("Failed to crate theme config file")); -} - -fn default_styles() -> Vec { - vec![default::build_theme(), railscasts::build_theme()] -} diff --git a/src/themes/mod.rs b/src/themes/mod.rs deleted file mode 100644 index ef08ffb..0000000 --- a/src/themes/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::config::directories::*; -use sdl2::pixels::Color; -use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; -use serde_json; -use std::env; -use std::fs; -use std::path::PathBuf; - -pub mod caret_color; -pub mod code_highlighting_color; -pub mod config_creator; -pub mod diff_color; -pub mod predef; -pub mod serde_color; -pub mod theme; -pub mod theme_config; - -pub use crate::themes::caret_color::CaretColor; -pub use crate::themes::code_highlighting_color::CodeHighlightingColor; -pub use crate::themes::diff_color::DiffColor; -pub use crate::themes::serde_color::SerdeColor; -pub use crate::themes::theme::Theme; -pub use crate::themes::theme_config::ThemeConfig; diff --git a/src/themes/serde_color.rs b/src/themes/serde_color.rs deleted file mode 100644 index c269de1..0000000 --- a/src/themes/serde_color.rs +++ /dev/null @@ -1,26 +0,0 @@ -use sdl2::pixels::Color; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct SerdeColor { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} - -impl SerdeColor { - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } -} - -impl Into for &SerdeColor { - fn into(self) -> Color { - Color { - r: self.r, - g: self.g, - b: self.b, - a: self.a, - } - } -} diff --git a/src/themes/theme.rs b/src/themes/theme.rs deleted file mode 100644 index 496c529..0000000 --- a/src/themes/theme.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::config::directories::themes_dir; -use crate::themes::CaretColor; -use crate::themes::CodeHighlightingColor; -use crate::themes::DiffColor; -use crate::themes::SerdeColor; -use dirs; -use std::fs; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct Theme { - name: String, - background: SerdeColor, - caret: CaretColor, - code_highlighting: CodeHighlightingColor, - diff: DiffColor, -} - -impl Default for Theme { - fn default() -> Self { - use crate::themes::config_creator; - Self { - name: "default".to_string(), - background: SerdeColor::new(255, 255, 255, 0), - caret: CaretColor::default(), - code_highlighting: CodeHighlightingColor::default(), - diff: DiffColor::default(), - } - } -} - -impl Theme { - pub fn new( - name: String, - background: SerdeColor, - caret: CaretColor, - code_highlighting: CodeHighlightingColor, - diff: DiffColor, - ) -> Self { - Self { - name, - background, - caret, - code_highlighting, - diff, - } - } - - pub fn name(&self) -> &String { - &self.name - } - - pub fn background(&self) -> &SerdeColor { - &self.background - } - - pub fn caret(&self) -> &CaretColor { - &self.caret - } - - pub fn diff(&self) -> &DiffColor { - &self.diff - } - - pub fn code_highlighting(&self) -> &CodeHighlightingColor { - &self.code_highlighting - } - - pub fn load(theme_name: String) -> Self { - let home_dir = dirs::config_dir().unwrap(); - let mut config_dir = home_dir.clone(); - config_dir.push("rider"); - fs::create_dir_all(&config_dir) - .unwrap_or_else(|_| panic!("Cannot create config directory")); - Self::load_content(format!("{}.json", theme_name).as_str()) - } - - fn load_content(file_name: &str) -> Theme { - let mut config_file = themes_dir(); - config_file.push(file_name); - let contents = match fs::read_to_string(&config_file) { - Ok(s) => s, - Err(_) => { - use crate::themes::config_creator; - config_creator::create(); - fs::read_to_string(&config_file) - .unwrap_or_else(|_| panic!("Failed to load theme config file")) - } - }; - serde_json::from_str(&contents).unwrap_or_default() - } -} diff --git a/src/themes/theme_config.rs b/src/themes/theme_config.rs deleted file mode 100644 index 66e542a..0000000 --- a/src/themes/theme_config.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::themes::SerdeColor; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct ThemeConfig { - color: SerdeColor, - italic: bool, - bold: bool, -} - -impl ThemeConfig { - pub fn new(color: SerdeColor, italic: bool, bold: bool) -> Self { - Self { - color, - italic, - bold, - } - } - - pub fn color(&self) -> &SerdeColor { - &self.color - } - - pub fn italic(&self) -> bool { - self.italic - } - - pub fn bold(&self) -> bool { - self.bold - } -} diff --git a/assets/examples/example.txt b/test_files/example.txt similarity index 100% rename from assets/examples/example.txt rename to test_files/example.txt diff --git a/assets/examples/test.rs b/test_files/test.rs similarity index 100% rename from assets/examples/test.rs rename to test_files/test.rs