Gh 3/open file (#15)

* Remove unused imports

Start FileSystem UI

Fix formating

Add textures for dir and file, implement prepare_ui, render, update and open directory

Display choose file

Display files and directories

Format code

Expand and collapse directories, open file

Format code

Fix calculating size of directory and displaying children

Refactor render open file modal

Format code

Scroll file tree

Format code

Refactor open file modal

Fix CI

Fix some tests, add more tests, fix formatting

Fix CI test run

Fix CI test run

* Add more tests

* Add more tests

* More tests

* More tests

* Fix caret position

* Add simple string matching

* Fixing add characters

* Fix themes images

* Simplify

* Simplify

* Simplify

* Fix some problems

* Fix race conditions in tests

* Format code

* Format code
This commit is contained in:
Adrian Woźniak 2019-02-27 17:59:57 +01:00 committed by GitHub
parent 0380e3c9fd
commit c2e1369738
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 4009 additions and 1582 deletions

View File

@ -9,6 +9,7 @@ jobs:
environment: environment:
CODECOV_TOKEN: "e58da505-19f2-481c-8068-e845cb36fbe4" CODECOV_TOKEN: "e58da505-19f2-481c-8068-e845cb36fbe4"
TZ: "/usr/share/zoneinfo/Europe/Paris" TZ: "/usr/share/zoneinfo/Europe/Paris"
rider-config: "1"
steps: steps:
- checkout - checkout
@ -31,15 +32,23 @@ jobs:
command: | command: |
rustup run nightly rustc --version --verbose rustup run nightly rustc --version --verbose
rustup run nightly cargo --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: - run:
name: Test and code coverage name: Test and code coverage
command: | command: |
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin || echo 0
Xvfb :5 -screen 0 800x600x24 +extension GLX +render -noreset &
export DISPLAY=:5
export XDG_RUNTIME_DIR=$(pwd) 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: - run:
name: Upload Coverage name: Upload Coverage
command: | command: |

78
Cargo.lock generated
View File

@ -1,3 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.6.9" version = "0.6.9"
@ -413,13 +415,24 @@ dependencies = [
[[package]] [[package]]
name = "rider" name = "rider"
version = "0.1.0" 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 = [ dependencies = [
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "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 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_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)", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.13" version = "0.1.13"
@ -590,6 +657,14 @@ name = "utf8-ranges"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.6.0" 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 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 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 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 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 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" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"

View File

@ -4,17 +4,25 @@ version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"] authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
edition = "2018" 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] [dependencies]
rand = "0.5" rider-config = { version = "*", path = "./rider-config" }
plex = "*" uuid = { version = "0.7", features = ["v4"] }
dirs = "*"
serde = "*"
serde_json = "*"
serde_derive = "*"
log = "*"
env_logger = "*"
simplelog = "*"
lazy_static = "*"
[dependencies.sdl2] [dependencies.sdl2]
version = "0.31.0" version = "0.31.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -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

23
rider-config/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "rider-config"
version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
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"]

242
rider-config/src/config.rs Normal file
View File

@ -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<String, Language>;
#[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<String> = mapping.keys().map(|s| s.to_string()).collect();
let mut expected: Vec<String> =
vec![".".to_string(), "txt".to_string(), "rs".to_string()];
keys.sort();
expected.sort();
assert_eq!(keys, expected);
}
{
let mut keys: Vec<Language> = mapping.values().map(|s| s.clone()).collect();
let mut expected: Vec<Language> =
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<String> = config
.extensions_mapping()
.keys()
.map(|s| s.to_owned())
.collect();
result.sort();
let mut expected: Vec<String> = vec!["rs".to_string(), "txt".to_string(), ".".to_string()];
expected.sort();
assert_eq!(result, expected);
}
}

View File

@ -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<String>, project_dir: Option<String>) -> 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<PathBuf, String> {
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<String, String> {
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);
}
}

View File

@ -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);
}
}

View File

@ -1,15 +1,17 @@
extern crate rider_lexers;
extern crate rider_themes;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
pub mod config; pub mod config;
pub(crate) mod creator;
pub mod directories; pub mod directories;
pub mod editor_config; pub mod editor_config;
pub mod scroll_config; pub mod scroll_config;
pub use crate::config::config::*; pub use crate::config::*;
pub use crate::config::directories::*; pub use crate::directories::*;
pub use crate::config::editor_config::*; pub use crate::editor_config::*;
pub use crate::config::scroll_config::*; pub use crate::scroll_config::*;
pub type ConfigAccess = Arc<RwLock<Config>>; pub type ConfigAccess = Arc<RwLock<Config>>;

View File

@ -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 { mod tests {
use super::*; use super::*;
@ -92,5 +103,4 @@ mod tests {
let expected = 98; let expected = 98;
assert_eq!(result, expected); assert_eq!(result, expected);
} }
} }

22
rider-editor/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "rider-editor"
version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
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"]

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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<EditorFile>,
config: Arc<RwLock<Config>>,
file_editor: FileEditor,
open_file_modal: Option<OpenFile>,
}
impl AppState {
pub fn new(config: Arc<RwLock<Config>>) -> 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<OpenFile>) {
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);
}
}

View File

@ -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<Window>;
#[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<RwLock<Config>>,
clear_color: Color,
sdl_context: Sdl,
canvas: WindowCanvas,
video_subsystem: VideoSubsystem,
tasks: Vec<UpdateResult>,
}
#[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<UpdateResult> = 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
}
}

View File

@ -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), ());
}
}

View File

@ -1,15 +1,16 @@
use crate::app::*; use crate::app::*;
use crate::renderer::Renderer; use crate::renderer::Renderer;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*; use sdl2::rect::{Point, Rect};
use std::sync::*; 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_editor
.file() .file()
.map_or_else(|| String::new(), |f| f.path()) .map_or_else(|| String::new(), |f| f.path())
} }
#[cfg_attr(tarpaulin, skip)]
pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
let mut buffer: String = if let Some(file) = file_editor.file() { let mut buffer: String = if let Some(file) = file_editor.file() {
file file
@ -35,7 +36,7 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
.file() .file()
.and_then(|f| f.get_character_at(file_editor.caret().text_position())) .and_then(|f| f.get_character_at(file_editor.caret().text_position()))
.and_then(|character| { .and_then(|character| {
let dest: &Rect = character.dest(); let dest: Rect = character.dest();
Some((position, Point::new(dest.x(), dest.y()))) Some((position, Point::new(dest.x(), dest.y())))
}); });
match move_to { 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); file_editor.replace_current_file(new_file);
} }
#[cfg_attr(tarpaulin, skip)]
pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
let file: &EditorFile = if let Some(file) = file_editor.file() { let file: &EditorFile = if let Some(file) = file_editor.file() {
file file
@ -68,6 +70,7 @@ pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
file_editor.replace_current_file(new_file); file_editor.replace_current_file(new_file);
} }
#[cfg_attr(tarpaulin, skip)]
pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) { 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()); let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer());
if buffer.is_empty() { if buffer.is_empty() {
@ -81,15 +84,19 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re
Some(c) => c, Some(c) => c,
_ => return, _ => 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(); let mut position: CaretPosition = file_editor.caret().position().clone();
for character in text.chars() { for character in text.chars() {
buffer.insert(position.text_position(), character); buffer.insert(position.text_position(), character);
if let Some(rect) = get_text_character_rect(character, renderer) { let rect = renderer.load_character_size(character);
pos = pos + Point::new(rect.width() as i32, 0); pos = pos + Point::new(rect.width() as i32, 0);
position = position.moved(1, 0, 0); position = position.moved(1, 0, 0);
file_editor.caret_mut().move_caret(position, pos.clone()); file_editor.caret_mut().move_caret(position, pos.clone());
}
} }
let mut new_file = EditorFile::new( 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); file_editor.replace_current_file(new_file);
} }
#[cfg_attr(tarpaulin, skip)]
pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
let mut buffer: String = if let Some(file) = file_editor.file() { let mut buffer: String = if let Some(file) = file_editor.file() {
file file
@ -115,17 +123,14 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
Some(c) => c, Some(c) => c,
_ => return, _ => return,
}; };
let mut pos = Point::new(current.dest().x(), current.dest().y()); let mut pos = Point::new(current.dest().x(), current.dest().y());
let mut position: CaretPosition = file_editor.caret().position().clone(); let mut position: CaretPosition = file_editor.caret().position().clone();
buffer.insert(position.text_position(), '\n'); buffer.insert(position.text_position(), '\n');
if let Some(rect) = get_text_character_rect('\n', renderer) { let rect = renderer.load_character_size('\n');
pos = Point::new( pos = Point::new(0, pos.y() + rect.height() as i32);
file_editor.config().read().unwrap().editor_left_margin(), position = position.moved(0, 1, 0);
pos.y() + rect.height() as i32, file_editor.caret_mut().move_caret(position, pos.clone());
);
position = position.moved(0, 1, 0);
file_editor.caret_mut().move_caret(position, pos.clone());
}
let mut new_file = EditorFile::new( let mut new_file = EditorFile::new(
current_file_path(file_editor), 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); new_file.prepare_ui(renderer);
file_editor.replace_current_file(new_file); 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());
}
}

57
rider-editor/src/main.rs Normal file
View File

@ -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<Box<SharedLogger>> = 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();
}

View File

@ -2,7 +2,8 @@ use sdl2::image::LoadTexture;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::render::{Texture, TextureCreator}; use sdl2::render::{Texture, TextureCreator};
use sdl2::ttf::{Font, Sdl2TtfContext}; 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::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -10,22 +11,41 @@ use std::env;
use std::hash::Hash; use std::hash::Hash;
use std::rc::Rc; use std::rc::Rc;
#[cfg_attr(tarpaulin, skip)]
//noinspection RsWrongLifetimeParametersNumber //noinspection RsWrongLifetimeParametersNumber
pub type RcTex<'l> = Rc<Texture<'l>>; pub type RcTex<'l> = Rc<Texture<'l>>;
#[cfg_attr(tarpaulin, skip)]
pub type RcFont<'l> = Rc<Font<'l, 'static>>; pub type RcFont<'l> = Rc<Font<'l, 'static>>;
#[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> { pub trait ResourceLoader<'l, R> {
type Args: ?Sized; type Args: ?Sized;
fn load(&'l self, data: &Self::Args) -> Result<R, String>; fn load(&'l self, data: &Self::Args) -> Result<R, String>;
} }
#[cfg_attr(tarpaulin, skip)]
#[derive(Debug, Hash, Eq, PartialEq, Clone)] #[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct FontDetails { pub struct FontDetails {
pub path: String, pub path: String,
pub size: u16, 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)] #[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub struct TextDetails { pub struct TextDetails {
pub text: String, pub text: String,
@ -33,6 +53,7 @@ pub struct TextDetails {
pub font: FontDetails, pub font: FontDetails,
} }
#[cfg_attr(tarpaulin, skip)]
impl TextDetails { impl TextDetails {
pub fn get_cache_key(&self) -> String { pub fn get_cache_key(&self) -> String {
format!( format!(
@ -43,6 +64,7 @@ impl TextDetails {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl<'a> From<&'a TextDetails> for TextDetails { impl<'a> From<&'a TextDetails> for TextDetails {
fn from(details: &'a Self) -> Self { fn from(details: &'a Self) -> Self {
Self { Self {
@ -53,6 +75,7 @@ impl<'a> From<&'a TextDetails> for TextDetails {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl FontDetails { impl FontDetails {
pub fn new(path: &str, size: u16) -> FontDetails { pub fn new(path: &str, size: u16) -> FontDetails {
Self { Self {
@ -62,6 +85,7 @@ impl FontDetails {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl<'a> From<&'a FontDetails> for FontDetails { impl<'a> From<&'a FontDetails> for FontDetails {
fn from(details: &'a FontDetails) -> Self { fn from(details: &'a FontDetails) -> Self {
Self { Self {
@ -71,16 +95,21 @@ impl<'a> From<&'a FontDetails> for FontDetails {
} }
} }
#[cfg_attr(tarpaulin, skip)]
//noinspection RsWrongLifetimeParametersNumber //noinspection RsWrongLifetimeParametersNumber
pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator<T>>; pub type TextureManager<'l> =
ResourceManager<'l, String, Texture<'l>, TextureCreator<sdl2::video::WindowContext>>;
#[cfg_attr(tarpaulin, skip)]
pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>;
#[cfg_attr(tarpaulin, skip)]
pub trait ManagersHolder<'l> { pub trait ManagersHolder<'l> {
fn font_manager(&mut self) -> &mut FontManager<'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)] #[derive(Clone)]
pub struct ResourceManager<'l, K, R, L> pub struct ResourceManager<'l, K, R, L>
where where
@ -91,6 +120,7 @@ where
cache: HashMap<K, Rc<R>>, cache: HashMap<K, Rc<R>>,
} }
#[cfg_attr(tarpaulin, skip)]
impl<'l, K, R, L> ResourceManager<'l, K, R, L> impl<'l, K, R, L> ResourceManager<'l, K, R, L>
where where
K: Hash + Eq, K: Hash + Eq,
@ -124,6 +154,7 @@ where
} }
} }
#[cfg_attr(tarpaulin, skip)]
//noinspection RsWrongLifetimeParametersNumber //noinspection RsWrongLifetimeParametersNumber
impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> { impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> {
type Args = str; type Args = str;
@ -134,6 +165,7 @@ impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
type Args = FontDetails; type Args = FontDetails;
@ -143,6 +175,7 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
} }
} }
#[cfg_attr(tarpaulin, skip)]
pub trait TextTextureManager<'l> { pub trait TextTextureManager<'l> {
//noinspection RsWrongLifetimeParametersNumber //noinspection RsWrongLifetimeParametersNumber
fn load_text( fn load_text(
@ -152,7 +185,8 @@ pub trait TextTextureManager<'l> {
) -> Result<Rc<Texture<'l>>, String>; ) -> Result<Rc<Texture<'l>>, String>;
} }
impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> { #[cfg_attr(tarpaulin, skip)]
impl<'l> TextTextureManager<'l> for TextureManager<'l> {
//noinspection RsWrongLifetimeParametersNumber //noinspection RsWrongLifetimeParametersNumber
fn load_text( fn load_text(
&mut self, &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 texture = self.loader.create_texture_from_surface(&surface).unwrap();
let resource = Rc::new(texture); let resource = Rc::new(texture);
self.cache.insert(key, resource.clone()); self.cache.insert(key, resource.clone());
for c in details.text.chars() { // for c in details.text.chars() {
info!("texture for '{:?}' created", c); // info!("texture for '{:?}' created", c);
} // }
Ok(resource) Ok(resource)
}, },
Ok, Ok,

View File

@ -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<TextCharacterDetails, Rect>,
}
#[cfg_attr(tarpaulin, skip)]
impl<'l> Renderer<'l> {
pub fn new(
config: ConfigAccess,
font_context: &'l Sdl2TtfContext,
texture_creator: &'l TextureCreator<sdl2::video::WindowContext>,
) -> 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<TextCharacterDetails, Rect> {
&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
}
}

11
rider-editor/src/tests.rs Normal file
View File

@ -0,0 +1,11 @@
#[cfg(test)]
pub mod support {
use rider_config::Config;
use std::sync::*;
pub fn build_config() -> Arc<RwLock<Config>> {
let mut config = Config::new();
config.set_theme(config.editor_config().current_theme().clone());
Arc::new(RwLock::new(config))
}
}

View File

@ -1,13 +1,9 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::text_character::TextCharacter;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color; use rider_config::ConfigAccess;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use sdl2::render::Texture;
use std::ops::Deref; use std::ops::Deref;
use std::sync::*;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Caret { pub struct Caret {
@ -70,13 +66,13 @@ impl Deref for Caret {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for Caret { impl Render for Caret {
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
use std::borrow::*; use std::borrow::*;
use std::option::*;
let dest = match context.borrow() { 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(), _ => self.dest().clone(),
}; };
let start = Point::new(dest.x(), dest.y()); 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 { fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
let dest = self.dest();
is_in_rect( is_in_rect(
point, point,
&match context { &match context {
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()), &UpdateContext::ParentPosition(p) => move_render_point(p, &dest),
_ => self.dest().clone(), _ => dest,
}, },
) )
} }
@ -136,18 +133,16 @@ impl RenderBox for Caret {
self.dest().top_left() self.dest().top_left()
} }
fn dest(&self) -> &Rect { fn dest(&self) -> Rect {
&self.dest self.dest.clone()
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_own_methods { mod test_own_methods {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::{Point, Rect};
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -204,11 +199,9 @@ mod test_own_methods {
#[cfg(test)] #[cfg(test)]
mod test_deref { mod test_deref {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Point;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -244,11 +237,9 @@ mod test_deref {
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod test_render_box {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Point;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -264,11 +255,9 @@ mod test_render_box {
#[cfg(test)] #[cfg(test)]
mod test_click_handler { mod test_click_handler {
use crate::app::*; use crate::app::*;
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Point;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]

View File

@ -23,7 +23,6 @@ impl CaretColor {
#[cfg(test)] #[cfg(test)]
mod test_getters { mod test_getters {
use super::*; use super::*;
use sdl2::pixels::*;
#[test] #[test]
fn assert_bright() { fn assert_bright() {

View File

@ -1,13 +1,12 @@
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::rc::Rc;
use std::sync::*; use std::sync::*;
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::Config;
use crate::renderer::Renderer; use crate::renderer::Renderer;
use crate::ui::file::editor_file_section::EditorFileSection; use crate::ui::file::editor_file_section::EditorFileSection;
use crate::ui::text_character::TextCharacter; use crate::ui::text_character::TextCharacter;
use crate::ui::*; use crate::ui::*;
use rider_config::Config;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EditorFile { pub struct EditorFile {
@ -114,6 +113,7 @@ impl TextCollection for EditorFile {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for EditorFile { impl Render for EditorFile {
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) {
for section in self.sections.iter() { for section in self.sections.iter() {
@ -196,21 +196,16 @@ impl RenderBox for EditorFile {
self.dest.top_left() self.dest.top_left()
} }
fn dest(&self) -> &Rect { fn dest(&self) -> Rect {
&self.dest self.dest.clone()
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod test_render_box {
use crate::app::*;
use crate::tests::support; use crate::tests::support;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*; use sdl2::rect::{Point, Rect};
use sdl2::*;
use std::borrow::*;
use std::rc::*;
use std::sync::*;
#[test] #[test]
fn assert_dest() { fn assert_dest() {

View File

@ -1,15 +1,14 @@
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::*; use std::sync::*;
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::Config;
use crate::lexer::Language;
use crate::renderer::Renderer; use crate::renderer::Renderer;
use crate::ui::file::editor_file_token::EditorFileToken; use crate::ui::file::editor_file_token::EditorFileToken;
use crate::ui::text_character::TextCharacter; use crate::ui::text_character::TextCharacter;
use crate::ui::*; use crate::ui::*;
use rider_config::Config;
use rider_lexers;
use rider_lexers::Language;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EditorFileSection { pub struct EditorFileSection {
@ -20,8 +19,6 @@ pub struct EditorFileSection {
impl EditorFileSection { impl EditorFileSection {
pub fn new(buffer: String, ext: String, config: Arc<RwLock<Config>>) -> Self { pub fn new(buffer: String, ext: String, config: Arc<RwLock<Config>>) -> Self {
use crate::lexer;
let language = config let language = config
.read() .read()
.unwrap() .unwrap()
@ -29,7 +26,7 @@ impl EditorFileSection {
.get(ext.as_str()) .get(ext.as_str())
.unwrap_or(&Language::PlainText) .unwrap_or(&Language::PlainText)
.clone(); .clone();
let lexer_tokens = lexer::parse(buffer.clone(), &language); let lexer_tokens = rider_lexers::parse(buffer.clone(), language);
let mut tokens: Vec<EditorFileToken> = vec![]; let mut tokens: Vec<EditorFileToken> = vec![];
let mut iterator = lexer_tokens.iter().peekable(); let mut iterator = lexer_tokens.iter().peekable();
@ -128,6 +125,7 @@ impl TextCollection for EditorFileSection {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for EditorFileSection { impl Render for EditorFileSection {
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) {
for token in self.tokens.iter() { for token in self.tokens.iter() {

View File

@ -1,38 +1,17 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; 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::renderer::*;
use crate::ui::*; use crate::ui::*;
use rider_config::Config;
use rider_lexers::TokenType;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use sdl2::render::Texture;
use sdl2::ttf::Font;
use std::rc::Rc;
use std::sync::*; use std::sync::*;
impl TokenType {
pub fn to_color(&self, config: &Arc<RwLock<Config>>) -> 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)] #[derive(Clone, Debug)]
pub struct EditorFileToken { pub struct EditorFileToken {
last_in_line: bool, last_in_line: bool,
characters: Vec<TextCharacter>, characters: Vec<TextCharacter>,
token_type: Rc<TokenType>, token_type: TokenType,
config: Arc<RwLock<Config>>, config: Arc<RwLock<Config>>,
} }
@ -41,7 +20,7 @@ impl EditorFileToken {
Self { Self {
last_in_line, last_in_line,
characters: vec![], characters: vec![],
token_type: Rc::new(token_type.clone()), token_type: token_type.clone(),
config, config,
} }
} }
@ -59,6 +38,21 @@ impl EditorFileToken {
text_character.update_position(current); text_character.update_position(current);
} }
} }
fn token_to_color(&self, config: &Arc<RwLock<Config>>) -> 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 { impl TextWidget for EditorFileToken {
@ -129,6 +123,7 @@ impl TextCollection for EditorFileToken {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for EditorFileToken { impl Render for EditorFileToken {
/** /**
* Must first create targets so even if new line appear renderer will know * 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() { if !self.characters.is_empty() {
return; return;
} }
let color: Color = self.token_type.to_color(renderer.config()); let color: Color = self.token_to_color(&renderer.config());
let chars: Vec<char> = self.token_type.text().chars().collect(); let chars: Vec<char> = self.token_type.text().chars().collect();
for (index, c) in chars.iter().enumerate() { for (index, c) in chars.iter().enumerate() {
let last_in_line = self.last_in_line && index + 1 == chars.len(); let last_in_line = self.last_in_line && index + 1 == chars.len();

View File

@ -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::*;
use crate::app::{UpdateResult as UR, WindowCanvas as WS}; use crate::app::{UpdateResult as UR, WindowCanvas as WS};
use crate::ui::scroll_bar::horizontal_scroll_bar::*; use crate::ui::scroll_bar::horizontal_scroll_bar::*;
use crate::ui::scroll_bar::vertical_scroll_bar::*; use crate::ui::scroll_bar::vertical_scroll_bar::*;
use crate::ui::scroll_bar::Scrollable; use crate::ui::scroll_bar::Scrollable;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::{Point, Rect};
use std::mem;
use std::sync::*;
pub struct FileEditor { pub struct FileEditor {
dest: Rect, dest: Rect,
@ -84,7 +81,7 @@ impl FileEditor {
} }
impl ScrollableView for 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 read_config = self.config.read().unwrap();
let value_x = read_config.scroll().speed() * x; let value_x = read_config.scroll().speed() * x;
@ -133,6 +130,8 @@ impl FileAccess for FileEditor {
if let Some(f) = self.file.as_ref() { if let Some(f) = self.file.as_ref() {
self.full_rect = f.full_rect(); self.full_rect = f.full_rect();
} }
self.vertical_scroll_bar.set_location(0);
self.horizontal_scroll_bar.set_location(0);
file file
} }
@ -162,7 +161,7 @@ impl CaretAccess for FileEditor {
fn move_caret(&mut self, dir: MoveDirection) { fn move_caret(&mut self, dir: MoveDirection) {
match dir { match dir {
MoveDirection::Left => {} MoveDirection::Left => caret_manager::move_caret_left(self),
MoveDirection::Right => caret_manager::move_caret_right(self), MoveDirection::Right => caret_manager::move_caret_right(self),
MoveDirection::Up => {} MoveDirection::Up => {}
MoveDirection::Down => {} MoveDirection::Down => {}
@ -197,6 +196,7 @@ impl CaretAccess for FileEditor {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for FileEditor { impl Render for FileEditor {
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) { fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) {
canvas.set_clip_rect(self.dest.clone()); canvas.set_clip_rect(self.dest.clone());
@ -215,12 +215,10 @@ impl Render for FileEditor {
); );
self.vertical_scroll_bar.render( self.vertical_scroll_bar.render(
canvas, canvas,
renderer,
&RenderContext::RelativePosition(self.dest.top_left()), &RenderContext::RelativePosition(self.dest.top_left()),
); );
self.horizontal_scroll_bar.render( self.horizontal_scroll_bar.render(
canvas, canvas,
renderer,
&RenderContext::RelativePosition(self.dest.top_left()), &RenderContext::RelativePosition(self.dest.top_left()),
); );
} }
@ -301,8 +299,8 @@ impl RenderBox for FileEditor {
self.dest.top_left() + self.scroll() self.dest.top_left() + self.scroll()
} }
fn dest(&self) -> &Rect { fn dest(&self) -> Rect {
&self.dest self.dest.clone()
} }
} }
@ -314,12 +312,8 @@ impl ConfigHolder for FileEditor {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::app::*;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*; use rider_config::Config;
use sdl2::*;
use std::borrow::*;
use std::rc::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -344,10 +338,6 @@ mod test_config_holder {
use crate::app::*; use crate::app::*;
use crate::tests::support; use crate::tests::support;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*;
use sdl2::*;
use std::borrow::*;
use std::rc::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -369,14 +359,9 @@ mod test_config_holder {
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod test_render_box {
use crate::app::*;
use crate::tests::support; use crate::tests::support;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*; use sdl2::rect::{Point, Rect};
use sdl2::*;
use std::borrow::*;
use std::rc::*;
use std::sync::*;
impl FileEditor { impl FileEditor {
pub fn set_full_rect(&mut self, r: Rect) { 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_dest(Rect::new(x.clone(), y.clone(), 999, 999));
widget.set_full_rect(Rect::new(0, 0, 99999, 99999)); widget.set_full_rect(Rect::new(0, 0, 99999, 99999));
widget.update(1, &UpdateContext::Nothing); widget.update(1, &UpdateContext::Nothing);
widget.scroll_to(30, 40); widget.scroll_by(30, 40);
let result = widget.render_start_point().clone(); let result = widget.render_start_point().clone();
let expected = Point::new(x - (ss * 30), y - (ss * 40)); let expected = Point::new(x - (ss * 30), y - (ss * 40));
assert_eq!(result, expected); assert_eq!(result, expected);

View File

@ -1,10 +1,9 @@
pub use crate::ui::file_editor::file_editor::*;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::*; use sdl2::rect::Point;
pub mod file_editor; pub mod file_editor;
pub use crate::ui::file_editor::file_editor::*;
pub trait FileAccess { pub trait FileAccess {
fn has_file(&self) -> bool; fn has_file(&self) -> bool;
@ -30,7 +29,7 @@ pub trait CaretAccess {
} }
pub trait ScrollableView { 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; fn scroll(&self) -> Point;
} }

View File

@ -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<FileEntry>,
directories: Vec<DirectoryView>,
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<T>(&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<T>(&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<T>(&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<T>(&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::<T>(canvas, renderer, &mut dest);
self.render_name::<T>(canvas, renderer, &mut dest.clone());
self.render_children::<T>(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
}
}

View File

@ -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<char, Rect>,
}
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<T>(&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<T>(&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<T>(&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())
}
}

View File

@ -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;

View File

@ -1,11 +1,9 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use rider_config::ConfigAccess;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::rc::Rc;
use std::sync::*;
pub struct MenuBar { pub struct MenuBar {
border_color: Color, border_color: Color,
@ -16,12 +14,17 @@ pub struct MenuBar {
impl MenuBar { impl MenuBar {
pub fn new(config: ConfigAccess) -> Self { 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(); 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 { Self {
border_color: Color::RGB(10, 10, 10), border_color,
background_color, background_color,
dest: Rect::new(0, 0, w as u32, h as u32), dest: Rect::new(0, 0, w as u32, h as u32),
config, config,
@ -33,6 +36,7 @@ impl MenuBar {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl Render for MenuBar { impl Render for MenuBar {
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
use std::borrow::*; use std::borrow::*;
@ -41,23 +45,23 @@ impl Render for MenuBar {
canvas.set_draw_color(self.background_color.clone()); canvas.set_draw_color(self.background_color.clone());
canvas canvas
.fill_rect(match context.borrow() { .fill_rect(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(), _ => self.dest(),
}) })
.unwrap_or_else(|_| panic!("Failed to draw main menu background")); .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 canvas
.draw_rect(match context.borrow() { .draw_rect(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(), _ => self.dest(),
}) })
.unwrap_or_else(|_| panic!("Failed to draw main menu background")); .unwrap_or_else(|_| panic!("Failed to draw main menu background"));
} }
fn prepare_ui(&mut self, _renderer: &mut Renderer) { fn prepare_ui(&mut self, _renderer: &mut Renderer) {
let width = self.config.read().unwrap().width(); 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); 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 { fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
let rect = match context.clone() { let rect = match *context {
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
_ => self.dest().clone(), _ => self.dest(),
}; };
is_in_rect(point, &rect) rect.contains_point(point.clone())
} }
} }
@ -89,19 +93,17 @@ impl RenderBox for MenuBar {
self.dest.top_left() self.dest.top_left()
} }
fn dest(&self) -> &Rect { fn dest(&self) -> Rect {
&self.dest self.dest
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_getters { mod test_getters {
use crate::app::*;
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::pixels::Color;
use sdl2::rect::*; use sdl2::rect::Rect;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -129,11 +131,9 @@ mod test_getters {
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod test_render_box {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Point;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -149,11 +149,9 @@ mod test_render_box {
#[cfg(test)] #[cfg(test)]
mod test_click_handler { mod test_click_handler {
use crate::app::*; use crate::app::*;
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Point;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]

View File

@ -1,14 +1,20 @@
use sdl2::rect::{Point, Rect}; 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::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::*;
use crate::renderer::managers::*; use crate::renderer::managers::*;
use crate::renderer::Renderer; use crate::renderer::Renderer;
use rider_config::*;
pub mod caret; pub mod caret;
pub mod file; pub mod file;
pub mod file_editor; pub mod file_editor;
pub mod filesystem;
pub mod menu_bar; pub mod menu_bar;
pub mod modal;
pub mod project_tree; pub mod project_tree;
pub mod scroll_bar; pub mod scroll_bar;
pub mod text_character; pub mod text_character;
@ -16,7 +22,9 @@ pub mod text_character;
pub use crate::ui::caret::*; pub use crate::ui::caret::*;
pub use crate::ui::file::*; pub use crate::ui::file::*;
pub use crate::ui::file_editor::*; pub use crate::ui::file_editor::*;
pub use crate::ui::filesystem::*;
pub use crate::ui::menu_bar::*; pub use crate::ui::menu_bar::*;
pub use crate::ui::modal::*;
pub use crate::ui::project_tree::*; pub use crate::ui::project_tree::*;
pub use crate::ui::scroll_bar::*; pub use crate::ui::scroll_bar::*;
pub use crate::ui::text_character::*; pub use crate::ui::text_character::*;
@ -34,6 +42,38 @@ pub enum RenderContext {
RelativePosition(Point), 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<Texture>, 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<Texture>, src: Rect, dest: Rect) -> Result<(), String> {
self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false)
}
}
#[inline] #[inline]
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool { pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
rect.contains_point(point.clone()) rect.contains_point(point.clone())
@ -47,7 +87,7 @@ where
let c = config_holder.config().read().unwrap(); let c = config_holder.config().read().unwrap();
FontDetails::new( FontDetails::new(
c.editor_config().font_path().as_str(), 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<Rect>
where where
T: ManagersHolder<'l> + ConfigHolder, T: ManagersHolder<'l> + ConfigHolder,
{ {
let font_details = build_font_details(renderer); let font_details = renderer.config().read().unwrap().editor_config().into();
renderer renderer
.font_manager() .font_manager()
.load(&font_details) .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()) Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height())
} }
#[cfg_attr(tarpaulin, skip)]
pub trait Render { 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); fn prepare_ui(&mut self, renderer: &mut Renderer);
} }
@ -88,14 +129,13 @@ pub trait ClickHandler {
pub trait RenderBox { pub trait RenderBox {
fn render_start_point(&self) -> Point; fn render_start_point(&self) -> Point;
fn dest(&self) -> &Rect; fn dest(&self) -> Rect;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::tests::support; use crate::tests::support;
use sdl2::rect::*;
struct ConfigWrapper { struct ConfigWrapper {
pub inner: ConfigAccess, pub inner: ConfigAccess,

View File

@ -0,0 +1,3 @@
pub mod open_file;
pub use crate::ui::modal::open_file::OpenFile;

View File

@ -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<T>(&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
}
}

View File

@ -1,9 +1,8 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::config::*;
use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use rider_config::ConfigAccess;
use sdl2::rect::*; use sdl2::pixels::Color;
use sdl2::rect::Rect;
pub struct HorizontalScrollBar { pub struct HorizontalScrollBar {
scroll_value: i32, scroll_value: i32,
@ -54,22 +53,26 @@ impl Update for HorizontalScrollBar {
} }
} }
impl Render for HorizontalScrollBar { #[cfg_attr(tarpaulin, skip)]
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { impl HorizontalScrollBar {
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
where
T: RenderRect,
{
if self.full_width < self.viewport { if self.full_width < self.viewport {
return; return;
} }
canvas.set_draw_color(Color::RGBA(255, 255, 255, 0));
canvas canvas
.fill_rect(match context { .render_rect(
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), match context {
_ => self.rect.clone(), 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")); .unwrap_or_else(|_| panic!("Failed to render vertical scroll back"));
} }
fn prepare_ui(&mut self, _renderer: &mut Renderer) {}
} }
impl Scrollable for HorizontalScrollBar { impl Scrollable for HorizontalScrollBar {

View File

@ -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 horizontal_scroll_bar;
pub mod vertical_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 { pub trait Scrollable {
fn scroll_to(&mut self, n: i32); fn scroll_to(&mut self, n: i32);

View File

@ -1,9 +1,8 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::config::*;
use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use rider_config::ConfigAccess;
use sdl2::rect::*; use sdl2::pixels::Color;
use sdl2::rect::Rect;
pub struct VerticalScrollBar { pub struct VerticalScrollBar {
scroll_value: i32, scroll_value: i32,
@ -54,22 +53,26 @@ impl Update for VerticalScrollBar {
} }
} }
impl Render for VerticalScrollBar { #[cfg_attr(tarpaulin, skip)]
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { impl VerticalScrollBar {
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
where
T: RenderBorder,
{
if self.full_height() < self.viewport() { if self.full_height() < self.viewport() {
return; return;
} }
canvas.set_draw_color(Color::RGBA(255, 255, 255, 0));
canvas canvas
.fill_rect(match context { .render_border(
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect), match context {
_ => self.rect.clone(), 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")); .unwrap_or_else(|_| panic!("Failed to render vertical scroll back"));
} }
fn prepare_ui(&mut self, _renderer: &mut Renderer) {}
} }
impl Scrollable for VerticalScrollBar { impl Scrollable for VerticalScrollBar {
@ -94,6 +97,9 @@ impl Scrollable for VerticalScrollBar {
} }
fn scrolled_part(&self) -> f64 { 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 self.scroll_value().abs() as f64 / (self.full_height() - self.viewport()) as f64
} }
} }

View File

@ -1,17 +1,16 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::{UpdateResult as UR, WindowCanvas as WC};
use crate::config::*;
use crate::lexer::TokenType;
use crate::renderer::managers::*; use crate::renderer::managers::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::caret::CaretPosition; use crate::ui::caret::CaretPosition;
use crate::ui::*; use crate::ui::*;
use rider_config::{ConfigAccess, ConfigHolder};
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use sdl2::render::Texture;
use sdl2::ttf::Font; pub trait CharacterSizeManager {
use std::rc::Rc; fn load_character_size(&mut self, c: char) -> Rect;
use std::sync::*; }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TextCharacter { pub struct TextCharacter {
@ -69,10 +68,10 @@ impl TextCharacter {
pub fn update_position(&mut self, current: &mut Rect) { pub fn update_position(&mut self, current: &mut Rect) {
if self.is_new_line() { if self.is_new_line() {
let y = self.source.height() as i32; 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_x(current.x());
self.dest.set_y(current.y()); self.dest.set_y(current.y());
current.set_x(0);
current.set_y(current.y() + y);
} else { } else {
self.dest.set_x(current.x()); self.dest.set_x(current.x());
self.dest.set_y(current.y()); 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 * Must first create targets so even if new line appear renderer will know
* where move render starting point * 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() { if self.is_new_line() {
return; return;
} }
let font_details = { let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into();
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 c = self.text_character.clone(); let c = self.text_character.clone();
let mut details = TextDetails { let mut details = TextDetails {
@ -127,9 +119,14 @@ impl Render for TextCharacter {
font: font_details.clone(), font: font_details.clone(),
}; };
let dest = match context { let dest = match context {
RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()), RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
_ => self.dest.clone(), _ => 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) { if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) {
canvas canvas
.copy_ex( .copy_ex(
@ -148,27 +145,26 @@ impl Render for TextCharacter {
// canvas.draw_rect(dest.clone()).unwrap(); // canvas.draw_rect(dest.clone()).unwrap();
} }
fn prepare_ui(&mut self, renderer: &mut Renderer) { pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T)
let font_details = build_font_details(renderer); where
T: ConfigHolder + CharacterSizeManager + ManagersHolder<'l>,
{
let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into();
let font = renderer let rect = renderer.load_character_size(self.text_character);
.font_manager() self.set_source(&rect);
.load(&font_details) self.set_dest(&rect);
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
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 { let mut details = TextDetails {
text: self.text_character.to_string(), text: self.text_character.to_string(),
color: self.color.clone(), color: self.color.clone(),
font: font_details.clone(), font: font_details.clone(),
}; };
let font = renderer
.font_manager()
.load(&font_details)
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
renderer renderer
.texture_manager() .texture_manager()
.load_text(&mut details, &font) .load_text(&mut details, &font)
@ -191,13 +187,11 @@ impl ClickHandler for TextCharacter {
} }
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
is_in_rect( let rect = match context {
point, &UpdateContext::ParentPosition(p) => move_render_point(p, &self.dest),
&match context { _ => self.dest(),
&UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), };
_ => self.dest().clone(), is_in_rect(point, &rect)
},
)
} }
} }
@ -206,18 +200,17 @@ impl RenderBox for TextCharacter {
self.dest.top_left() self.dest.top_left()
} }
fn dest(&self) -> &Rect { fn dest(&self) -> Rect {
&self.dest self.dest
} }
} }
#[cfg(test)] #[cfg(test)]
mod test_getters { mod test_getters {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::pixels::Color;
use sdl2::rect::*; use sdl2::rect::Rect;
use std::sync::*; use std::sync::*;
#[test] #[test]
@ -323,7 +316,7 @@ mod test_getters {
Color::RGB(1, 12, 123), Color::RGB(1, 12, 123),
Arc::clone(&config), Arc::clone(&config),
); );
assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0)); assert_eq!(widget.dest(), Rect::new(0, 0, 0, 0));
} }
#[test] #[test]
@ -343,38 +336,48 @@ mod test_getters {
#[cfg(test)] #[cfg(test)]
mod test_own_methods { mod test_own_methods {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::Rect;
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
fn must_update_position_of_new_line() { fn must_update_position_of_new_line() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let mut current = Rect::new(10, 23, 0, 0); let mut current = Rect::new(10, 23, 0, 0);
widget.update_position(&mut current); widget.update_position(&mut current);
assert_eq!(current, Rect::new(0, 103, 1, 1)); 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)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80));
} }
#[test] #[test]
fn must_update_position_of_non_new_line() { fn must_update_position_of_non_new_line() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('W', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let mut current = Rect::new(10, 23, 0, 0); let mut current = Rect::new(10, 23, 0, 0);
widget.update_position(&mut current); widget.update_position(&mut current);
assert_eq!(current, Rect::new(80, 23, 1, 1)); 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)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80));
} }
} }
@ -382,18 +385,22 @@ mod test_own_methods {
#[cfg(test)] #[cfg(test)]
mod test_click_handler { mod test_click_handler {
use crate::app::*; use crate::app::*;
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::{Point, Rect};
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
fn refute_when_not_click_target() { fn refute_when_not_click_target() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let point = Point::new(0, 0); let point = Point::new(0, 0);
@ -405,8 +412,14 @@ mod test_click_handler {
#[test] #[test]
fn assert_when_click_target() { fn assert_when_click_target() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let point = Point::new(20, 30); let point = Point::new(20, 30);
@ -418,8 +431,14 @@ mod test_click_handler {
#[test] #[test]
fn refute_when_not_click_target_because_parent() { fn refute_when_not_click_target_because_parent() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let point = Point::new(20, 30); let point = Point::new(20, 30);
@ -431,8 +450,14 @@ mod test_click_handler {
#[test] #[test]
fn assert_when_click_target_because_parent() { fn assert_when_click_target_because_parent() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let point = Point::new(120, 130); let point = Point::new(120, 130);
@ -451,7 +476,7 @@ mod test_click_handler {
position.clone(), position.clone(),
line.clone(), line.clone(),
true, true,
Color::RGB(0, 0, 0), sdl2::pixels::Color::RGB(0, 0, 0),
Arc::clone(&config), Arc::clone(&config),
); );
let dest = Rect::new(10, 20, 30, 40); let dest = Rect::new(10, 20, 30, 40);
@ -468,18 +493,22 @@ mod test_click_handler {
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod test_render_box {
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::{Point, Rect};
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
fn must_return_top_left_point() { fn must_return_top_left_point() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let result = widget.render_start_point(); let result = widget.render_start_point();
@ -491,18 +520,22 @@ mod test_render_box {
#[cfg(test)] #[cfg(test)]
mod test_update { mod test_update {
use crate::app::*; use crate::app::*;
use crate::renderer::*;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::*; use sdl2::rect::{Point, Rect};
use sdl2::rect::*;
use std::sync::*; use std::sync::*;
#[test] #[test]
fn assert_do_nothing() { fn assert_do_nothing() {
let config = support::build_config(); let config = support::build_config();
let mut widget = let mut widget = TextCharacter::new(
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config)); '\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_dest(&Rect::new(10, 20, 30, 40));
widget.set_source(&Rect::new(50, 60, 70, 80)); widget.set_source(&Rect::new(50, 60, 70, 80));
let result = widget.update( let result = widget.update(

View File

@ -0,0 +1,17 @@
[package]
name = "rider-generator"
version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -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
);
}
}

View File

@ -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);
}
}

120
rider-generator/src/main.rs Normal file
View File

@ -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);
}
}

View File

@ -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<Theme> {
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
);
}
}

View File

@ -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<u8> = 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);
}
}

10
rider-lexers/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "rider-lexers"
version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
edition = "2018"
[dependencies]
plex = "*"
log = "*"
simplelog = "*"

View File

@ -1,3 +1,6 @@
extern crate log;
extern crate simplelog;
use std::ops::Deref; use std::ops::Deref;
pub mod plain; pub mod plain;
@ -114,19 +117,19 @@ impl Token {
} }
pub fn line(&self) -> usize { pub fn line(&self) -> usize {
self.line.clone() self.line
} }
pub fn character(&self) -> usize { pub fn character(&self) -> usize {
self.character.clone() self.character
} }
pub fn start(&self) -> usize { pub fn start(&self) -> usize {
self.start.clone() self.start
} }
pub fn end(&self) -> usize { pub fn end(&self) -> usize {
self.end.clone() self.end
} }
pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { 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<TokenType> { pub fn parse(text: String, language: Language) -> Vec<TokenType> {
match language { match language {
&Language::PlainText => plain::lexer::Lexer::new(text.as_str()) Language::PlainText => plain::lexer::Lexer::new(text.as_str())
.inspect(|tok| warn!("tok: {:?}", tok)) // .inspect(|tok| warn!("tok: {:?}", tok))
.map(|t| t.0) .map(|t| t.0)
.collect(), .collect(),
&Language::Rust => rust_lang::lexer::Lexer::new(text.as_str()) Language::Rust => rust_lang::lexer::Lexer::new(text.as_str())
.inspect(|tok| warn!("tok: {:?}", tok)) // .inspect(|tok| warn!("tok: {:?}", tok))
.map(|t| t.0) .map(|t| t.0)
.collect(), .collect(),
} }
@ -155,12 +158,13 @@ pub fn parse(text: String, language: &Language) -> Vec<TokenType> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::lexer::*; use super::*;
use crate::Token;
#[test] #[test]
fn must_parse_plain() { fn must_parse_plain() {
let buffer = "foo bar"; let buffer = "foo bar";
let language = &Language::PlainText; let language = Language::PlainText;
let result = parse(buffer.to_string(), language); let result = parse(buffer.to_string(), language);
assert_eq!(result.len(), 3); assert_eq!(result.len(), 3);
} }
@ -168,7 +172,7 @@ mod tests {
#[test] #[test]
fn must_parse_rust() { fn must_parse_rust() {
let buffer = "foo bar"; let buffer = "foo bar";
let language = &Language::Rust; let language = Language::Rust;
let result = parse(buffer.to_string(), language); let result = parse(buffer.to_string(), language);
assert_eq!(result.len(), 3); assert_eq!(result.len(), 3);
} }

View File

@ -1,7 +1,5 @@
use crate::lexer::{Token, TokenType};
pub mod lexer { pub mod lexer {
use crate::lexer::{Span, Token, TokenType}; use crate::{Span, Token, TokenType};
use plex::lexer; use plex::lexer;
lexer! { lexer! {
@ -10,6 +8,7 @@ pub mod lexer {
r"[ \t\r\n]" => (TokenType::Whitespace { r"[ \t\r\n]" => (TokenType::Whitespace {
token: Token::new(text.to_string(), 0, 0, 0, 0) token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text), }, text),
r"[^ \t\r\n]+" => (TokenType::Identifier { r"[^ \t\r\n]+" => (TokenType::Identifier {
token: Token::new(text.to_string(), 0, 0, 0, 0) token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text), }, text),
@ -37,31 +36,25 @@ pub mod lexer {
type Item = (TokenType, Span); type Item = (TokenType, Span);
fn next(&mut self) -> Option<(TokenType, Span)> { fn next(&mut self) -> Option<(TokenType, Span)> {
loop { let tok: (TokenType, &str) =
let tok: (TokenType, &str) = if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { self.remaining = new_remaining;
self.remaining = new_remaining; if token_type.is_new_line() {
if token_type.is_new_line() { self.line += 1;
self.line += 1; self.character = text.len();
self.character = text.len();
} else {
self.character += text.len();
}
(token_type, text)
} else { } else {
return None; self.character += text.len();
};
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));
} }
(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)] #[cfg(test)]
mod tests { mod tests {
use crate::lexer::plain::*; use super::*;
use crate::lexer::*; use crate::{Token, TokenType};
#[test] #[test]
fn must_parse_simple_text() { fn must_parse_simple_text() {

View File

@ -1,7 +1,5 @@
use crate::lexer::{Token, TokenType};
pub mod lexer { pub mod lexer {
use crate::lexer::{Span, Token, TokenType}; use crate::{Span, Token, TokenType};
use plex::lexer; use plex::lexer;
lexer! { lexer! {
@ -11,6 +9,10 @@ pub mod lexer {
token: Token::new(text.to_string(), 0, 0, 0, 0) token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text), }, text),
"(r\"|\")" => (TokenType::String {
token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text),
r"([0-9]+|[0-9]+\.[0-9]+|'[^']')" => (TokenType::Literal { r"([0-9]+|[0-9]+\.[0-9]+|'[^']')" => (TokenType::Literal {
token: Token::new(text.to_string(), 0, 0, 0, 0) token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text), }, text),
@ -30,6 +32,10 @@ pub mod lexer {
r"[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier { r"[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier {
token: Token::new(text.to_string(), 0, 0, 0, 0) token: Token::new(text.to_string(), 0, 0, 0, 0)
}, text), }, 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> { pub struct Lexer<'a> {
@ -54,31 +60,25 @@ pub mod lexer {
type Item = (TokenType, Span); type Item = (TokenType, Span);
fn next(&mut self) -> Option<(TokenType, Span)> { fn next(&mut self) -> Option<(TokenType, Span)> {
loop { let tok: (TokenType, &str) =
let tok: (TokenType, &str) = if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { self.remaining = new_remaining;
self.remaining = new_remaining; (token_type, text)
if token_type.is_new_line() { } else {
self.line += 1; return None;
self.character = text.len(); };
} else { match tok {
self.character += text.len(); (tok, text) => {
} let line = self.line;
(token_type, text) if tok.is_new_line() {
self.line += 1;
self.character = text.len();
} else { } else {
return None; self.character += text.len();
};
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));
} }
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)] #[cfg(test)]
mod tests { mod tests {
use crate::lexer::rust_lang::*; use super::*;
use crate::lexer::*; use crate::{Token, TokenType};
#[test] #[test]
fn must_parse_simple_text() { fn must_parse_simple_text() {
@ -207,7 +207,7 @@ mod tests {
token: Token::new("foo".to_string(), 0, 0, 0, 3), token: Token::new("foo".to_string(), 0, 0, 0, 3),
}, },
TokenType::Whitespace { TokenType::Whitespace {
token: Token::new("\n".to_string(), 1, 0, 3, 4), token: Token::new("\n".to_string(), 0, 0, 3, 4),
}, },
TokenType::Identifier { TokenType::Identifier {
token: Token::new("bar".to_string(), 1, 1, 4, 7), 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), token: Token::new("{".to_string(), 0, 30, 30, 31),
}, },
TokenType::Whitespace { TokenType::Whitespace {
token: Token::new("\n".to_string(), 1, 0, 31, 32), token: Token::new("\n".to_string(), 0, 0, 31, 32),
}, },
TokenType::Whitespace { TokenType::Whitespace {
token: Token::new(" ".to_string(), 1, 1, 32, 44), 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), token: Token::new("b".to_string(), 1, 17, 48, 49),
}, },
TokenType::Whitespace { TokenType::Whitespace {
token: Token::new("\n".to_string(), 2, 0, 49, 50), token: Token::new("\n".to_string(), 1, 0, 49, 50),
}, },
TokenType::Whitespace { TokenType::Whitespace {
token: Token::new(" ".to_string(), 2, 1, 50, 58), token: Token::new(" ".to_string(), 2, 1, 50, 58),

15
rider-themes/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "rider-themes"
version = "0.1.0"
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
edition = "2018"
[dependencies]
dirs = "*"
serde = "*"
serde_json = "*"
serde_derive = "*"
[dependencies.sdl2]
version = "0.31.0"
features = ["gfx", "image", "mixer", "ttf"]

View File

@ -1,5 +1,5 @@
use crate::themes::SerdeColor; use crate::SerdeColor;
use crate::themes::ThemeConfig; use crate::ThemeConfig;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct CaretColor { pub struct CaretColor {
@ -10,7 +10,7 @@ pub struct CaretColor {
impl Default for CaretColor { impl Default for CaretColor {
fn default() -> Self { fn default() -> Self {
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), blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
} }
} }

View File

@ -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);
}
}

View File

@ -1,5 +1,5 @@
use crate::themes::SerdeColor; use crate::SerdeColor;
use crate::themes::ThemeConfig; use crate::ThemeConfig;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct DiffColor { pub struct DiffColor {
@ -12,9 +12,9 @@ pub struct DiffColor {
impl Default for DiffColor { impl Default for DiffColor {
fn default() -> Self { fn default() -> Self {
Self { Self {
add: 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(0, 0, 0, 0), false, false), delete: ThemeConfig::new(SerdeColor::new(200, 0, 0, 0), false, false),
change: ThemeConfig::new(SerdeColor::new(0, 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), text: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
} }
} }

View File

@ -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);
}
}

21
rider-themes/src/lib.rs Normal file
View File

@ -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;

View File

@ -1,4 +1,4 @@
use crate::themes::Theme; use crate::Theme;
pub fn build_theme() -> Theme { pub fn build_theme() -> Theme {
Theme::default() Theme::default()

View File

@ -1,14 +1,16 @@
use crate::themes::caret_color::CaretColor; use crate::caret_color::CaretColor;
use crate::themes::CodeHighlightingColor; use crate::CodeHighlightingColor;
use crate::themes::DiffColor; use crate::DiffColor;
use crate::themes::SerdeColor; use crate::SerdeColor;
use crate::themes::Theme; use crate::Theme;
use crate::themes::ThemeConfig; use crate::ThemeConfig;
use crate::ThemeImages;
pub fn build_theme() -> Theme { pub fn build_theme() -> Theme {
Theme::new( Theme::new(
"railscasts".to_string(), "railscasts".to_string(),
SerdeColor::new(18, 18, 18, 0), SerdeColor::new(18, 18, 18, 0),
SerdeColor::new(200, 200, 200, 0),
CaretColor::new( CaretColor::new(
ThemeConfig::new(SerdeColor::new(121, 121, 121, 0), false, false), ThemeConfig::new(SerdeColor::new(121, 121, 121, 0), false, false),
ThemeConfig::new(SerdeColor::new(21, 21, 21, 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(135, 0, 135, 0), false, false),
ThemeConfig::new(SerdeColor::new(18, 18, 18, 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(),
),
) )
} }

View File

@ -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<Color> 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);
}
}

141
rider-themes/src/theme.rs Normal file
View File

@ -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);
}
}

View File

@ -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);
}
}

7
scripts/test.sh Executable file
View File

@ -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

View File

@ -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<EditorFile>,
config: Arc<RwLock<Config>>,
file_editor: FileEditor,
}
impl AppState {
pub fn new(config: Arc<RwLock<Config>>) -> 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
}
}

View File

@ -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<Window>;
#[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<RwLock<Config>>,
clear_color: Color,
sdl_context: Sdl,
canvas: WindowCanvas,
video_subsystem: VideoSubsystem,
tasks: Vec<Task>,
}
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
}
}

View File

@ -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();
}

View File

@ -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<String, Language>;
#[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<String> = mapping.keys().map(|s| s.to_string()).collect();
let mut expected: Vec<String> =
vec![".".to_string(), "txt".to_string(), "rs".to_string()];
keys.sort();
expected.sort();
assert_eq!(keys, expected);
}
{
let mut keys: Vec<Language> = mapping.values().map(|s| s.clone()).collect();
let mut expected: Vec<Language> =
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);
}
}

View File

@ -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!"));
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -1,59 +1,53 @@
#![allow(unused_imports)] extern crate rider_config;
use std::process::Command;
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<Box<SharedLogger>> = 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();
}
fn main() { fn main() {
let mut app = Application::new(); let generator = rider_config::directories::get_binary_path("rider-generator").unwrap();
app.init(); println!("generator will be {:?}", generator);
init_logger(); Command::new(generator).status().unwrap();
app.open_file("./assets/examples/test.rs".to_string());
app.run(); 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);
}
} }

View File

@ -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<WinCtxt>,
) -> 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
}
}

View File

@ -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<RwLock<Config>> {
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()
}
}

View File

@ -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
}
}

View File

@ -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<Theme> {
vec![default::build_theme(), railscasts::build_theme()]
}

View File

@ -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;

View File

@ -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<Color> for &SerdeColor {
fn into(self) -> Color {
Color {
r: self.r,
g: self.g,
b: self.b,
a: self.a,
}
}
}

View File

@ -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()
}
}

Some files were not shown because too many files have changed in this diff Show More