├── .gitignore ├── Cargo.toml ├── LICENSE ├── src ├── error.rs ├── node_view.rs ├── lib.rs ├── utils.rs ├── content.rs └── render.rs ├── ftplugin └── graphics.vim ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vim-graphical-preview" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | name = "vim_graphical_preview" 10 | crate-type = ["cdylib"] # Creates dynamic lib 11 | 12 | [dependencies] 13 | miniserde = "0.1" 14 | magick_rust = {version="0.15", features=["disable-hdri"]} 15 | regex = "1.5.4" 16 | sha2 = "0.10" 17 | which = "4" 18 | nix = "0.23" 19 | flame = "0.2" 20 | 21 | [profile.release] 22 | lto = true 23 | codegen-units = 1 24 | panic = "abort" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{io, result}; 2 | use std::path::PathBuf; 3 | use std::fmt; 4 | 5 | pub type Result = result::Result; 6 | 7 | #[derive(Debug)] 8 | pub enum Error { 9 | InvalidMath(String, String, usize), // reason, element, line 10 | InvalidDvisvgm(String), 11 | FileNotFound(PathBuf), 12 | BinaryNotFound(which::Error), 13 | UnknownFence(String), 14 | InvalidImage(String), 15 | Io(io::Error), 16 | } 17 | 18 | impl fmt::Display for Error { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | let res = match self { 21 | Error::InvalidMath(reason, element, line) => 22 | format!("could not parse math {} at {} bc. {}", element, line, reason), 23 | Error::InvalidDvisvgm(err) => 24 | err.to_string(), 25 | Error::FileNotFound(path) => 26 | format!("could not find file {}", path.to_str().unwrap()), 27 | Error::BinaryNotFound(binary) => 28 | format!("binary not found: {}", binary), 29 | Error::UnknownFence(kind) => 30 | format!("unknown fence with name {}", kind), 31 | Error::InvalidImage(path) => 32 | format!("could not read in {} as image", path), 33 | Error::Io(io_err) => format!("IO error: {}", io_err) 34 | }; 35 | 36 | write!(f, "{}", res) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/node_view.rs: -------------------------------------------------------------------------------- 1 | use crate::render::Metadata; 2 | use crate::content::Node; 3 | 4 | #[derive(PartialEq, Eq, Debug)] 5 | pub enum NodeView { 6 | Hidden, 7 | UpperBorder(usize, usize), 8 | LowerBorder(usize, usize), 9 | Visible(usize, usize), 10 | } 11 | 12 | impl NodeView { 13 | pub fn new(node: &Node, metadata: &Metadata, offset: isize) -> NodeView { 14 | let start; 15 | let mut height = node.range.1 - node.range.0 + 1; 16 | 17 | if offset <= -(height as isize) { 18 | // if we are above the upper line, just skip 19 | return NodeView::Hidden; 20 | } else if offset < 0 { 21 | // if we are in the upper cross-over region, calculate the visible height 22 | start = (-offset) as usize; 23 | height -= start; 24 | return NodeView::UpperBorder(start, height); 25 | } 26 | 27 | let distance_lower = metadata.viewport.0 as isize - offset; 28 | 29 | //dbg!(&offset, &height, &distance_lower); 30 | 31 | if distance_lower <= 0 { 32 | return NodeView::Hidden; 33 | } else if (distance_lower as usize) < height { 34 | // remove some height if we are in the command line region 35 | height -= (height as isize - distance_lower) as usize; 36 | start = offset as usize; 37 | return NodeView::LowerBorder(start, height); 38 | } 39 | 40 | NodeView::Visible(offset as usize, height) 41 | } 42 | 43 | pub fn is_visible(&self) -> bool { 44 | self != &NodeView::Hidden 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::os::raw::c_char; 3 | 4 | use std::sync::Once; 5 | use std::cell::RefCell; 6 | use std::mem::MaybeUninit; 7 | 8 | mod error; 9 | mod utils; 10 | mod render; 11 | mod content; 12 | mod node_view; 13 | 14 | use error::Result; 15 | 16 | struct SingletonReader { 17 | inner: RefCell, 18 | } 19 | 20 | fn singleton() -> &'static SingletonReader { 21 | // Create an uninitialized static 22 | static mut SINGLETON: MaybeUninit = MaybeUninit::uninit(); 23 | static ONCE: Once = Once::new(); 24 | 25 | unsafe { 26 | ONCE.call_once(|| { 27 | // Make it 28 | let singleton = SingletonReader { 29 | inner: RefCell::new(render::Render::new()), 30 | }; 31 | // Store it to the static var, i.e. initialize it 32 | SINGLETON.write(singleton); 33 | }); 34 | 35 | // Now we give out a shared reference to the data, which is safe to use 36 | // concurrently. 37 | SINGLETON.assume_init_ref() 38 | } 39 | } 40 | 41 | pub fn result_to_cstring(res: Result) -> CString { 42 | let inner = match res { 43 | Ok(inn) => format!("{{ \"ok\": {} }}", inn.to_string()), 44 | Err(err) => format!("{{ \"err\": \"{}\" }}", err.to_string()), 45 | }; 46 | 47 | CString::new(inner).unwrap() 48 | } 49 | 50 | macro_rules! export_fn { 51 | ($fn_name:ident,String)=> { 52 | #[no_mangle] 53 | pub unsafe extern "C" fn $fn_name(input: *const c_char) -> *const c_char { 54 | let input = CStr::from_ptr(input); 55 | let in_str = input.to_str().unwrap(); 56 | 57 | let res = singleton().inner.borrow_mut().$fn_name(in_str); 58 | let res_str = result_to_cstring(res); 59 | 60 | res_str.into_raw() 61 | } 62 | }; 63 | ($fn_name:ident,usize) => { 64 | #[no_mangle] 65 | pub unsafe extern "C" fn $fn_name(input: *const c_char) -> usize { 66 | let input = CStr::from_ptr(input); 67 | let in_str = input.to_str().unwrap(); 68 | 69 | match singleton().inner.borrow_mut().$fn_name(in_str) 70 | } 71 | }; 72 | ($fn_name:ident,()) => { 73 | #[no_mangle] 74 | pub unsafe extern "C" fn $fn_name(input: *const c_char) { 75 | let input = CStr::from_ptr(input); 76 | let in_str = input.to_str().unwrap(); 77 | 78 | singleton().inner.borrow_mut().$fn_name(in_str).unwrap(); 79 | } 80 | } 81 | } 82 | 83 | export_fn!(update_content, String); 84 | export_fn!(update_metadata, ()); 85 | export_fn!(clear_all, ()); 86 | export_fn!(draw, String); 87 | export_fn!(set_folds, ()); 88 | -------------------------------------------------------------------------------- /ftplugin/graphics.vim: -------------------------------------------------------------------------------- 1 | if exists("g:loaded_graphical_preview") 2 | echo "Exists" 3 | finish 4 | endif 5 | let g:loaded_graphical_preview = 1 6 | 7 | let s:path = resolve(expand(':p:h') . "/../") 8 | let s:inst = libcallex#load(s:path . "/target/release/libvim_graphical_preview.so") 9 | let s:folds = [] 10 | 11 | function! PrintError(msg) abort 12 | execute 'normal! \' 13 | echohl ErrorMsg 14 | echomsg a:msg 15 | echohl None 16 | endfunction 17 | 18 | function! DrawInner(id) 19 | let res = s:inst.call("draw", [""], "string") 20 | let res = json_decode(res) 21 | 22 | if has_key(res, 'err') 23 | call PrintError("Error: " . res['err']) 24 | elseif has_key(res, 'ok') && res['ok'] == 1 25 | call Draw() 26 | endif 27 | endfunction 28 | 29 | function! Draw() 30 | if exists("g:timer") 31 | call timer_stop(g:timer) 32 | endif 33 | 34 | let g:timer = timer_start(50, "DrawInner") 35 | endfunction 36 | 37 | function! s:UpdateMetadata() 38 | let winpos = win_screenpos("0") 39 | if exists('&number') && &number == 1 40 | let winpos[1] += &numberwidth 41 | endif 42 | 43 | let metadata = { 44 | \'file_range': [line("w0"), line("w$") - &cmdheight + 1], 45 | \'viewport': [&lines - &cmdheight - 1, &columns], 46 | \'cursor': getcurpos()[1], 47 | \'winpos': winpos, 48 | \'char_height': 0, 49 | \} 50 | 51 | call s:inst.call("update_metadata", [json_encode(metadata)], "") 52 | call Draw() 53 | endfunction 54 | 55 | function! s:UpdateFolds() 56 | call s:UpdateMetadata() 57 | let l:folding_state = [] 58 | for lnum in s:folds 59 | call add(l:folding_state, [lnum, foldclosedend(lnum)]) 60 | endfor 61 | mode 62 | let any_changed = s:inst.call("set_folds", [json_encode(folding_state)], "") 63 | if any_changed 64 | call Draw() 65 | endif 66 | endfunction 67 | 68 | function! s:TextChanged() 69 | call s:UpdateMetadata() 70 | let current_buf = join(getline(1,'$'), "\n") 71 | let res = s:inst.call("update_content", [current_buf], "string") 72 | let res = json_decode(res)['ok'] 73 | if has_key(res, 'update_folding') 74 | let s:folds = res['update_folding'] 75 | call s:UpdateFolds() 76 | endif 77 | if res['should_redraw'] 78 | call Draw() 79 | endif 80 | endfunction 81 | 82 | function! s:ClearAll() 83 | call s:inst.call("clear_all", [""], "") 84 | mode 85 | endfunction 86 | 87 | :autocmd VimEnter,TextChanged,InsertLeave * call TextChanged() 88 | :autocmd VimResized * call UpdateMetadata() 89 | :autocmd CursorMoved * call UpdateMetadata() 90 | :autocmd InsertEnter * call ClearAll() 91 | 92 | map zo :foldopen:call UpdateFolds() 93 | map zc :foldclose:call UpdateFolds() 94 | map zO :foldopen!:call UpdateFolds() 95 | map zC :foldclose!:call UpdateFolds() 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Display graphics in (N)Vim with SIXEL characters 2 | 3 | Adds the ability to display graphics to (N)Vim. This is developed for my digital notebook and is still in infancy. Expect weird behaviour and crashes. 4 | 5 | The picture's content is streamed via the SIXEL character set (see [1](https://saitoha.github.io/libsixel/), [2](https://en.wikipedia.org/wiki/Sixel), [3](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) for more information) 6 | 7 | ## Features 8 | 9 | - [x] Render LaTex equations within math fences 10 | - [x] Display pictures in standalone image links 11 | - [x] Support GnuPlot, arbitrary LaTex, fetch source from other files 12 | - [ ] Fix weird artifacts of SIXEL 13 | 14 | ## Examples 15 | 16 | ### Graphic and LaTex math support 17 | 18 | https://user-images.githubusercontent.com/989353/187507544-9cbebd36-5a53-43eb-898a-5f64200d8b68.mp4 19 | 20 | ### Support folding 21 | 22 | https://user-images.githubusercontent.com/989353/187507582-f4daee70-ead3-4aba-a256-4d4c72223bbd.mp4 23 | 24 | ### Stream SIXEL characters via any text medium 25 | 26 | https://user-images.githubusercontent.com/989353/187510494-0416f52a-6b69-4e36-a901-d58aa0747d63.mp4 27 | 28 | ## Installation 29 | 30 | The installation is a bit more involved. You have to make sure that your terminal is able to render SIXEL characters. The plugin is written in Rust for performance reasons, and interfaced to Vimscript with [libcallex-vim](https://github.com/mattn/libcallex-vim). For this you also need to install toolchains for Rust and C. Imagemagick is also required to convert between different graphic formats and SIXEL. 31 | 32 | First use a terminal supporting SIXEL characters 33 | 34 | * Tested: Alacritty ([PR #4763](https://github.com/alacritty/alacritty/pull/4763) or merged in this [fork](https://github.com/microo8/alacritty-sixel)) 35 | * Not tested: XTerm, [others](https://saitoha.github.io/libsixel/) 36 | 37 | You have to install imagemagick to render graphics to SIXEL format: 38 | 39 | * Archlinux: `pacman -S imagemagick` 40 | * Ubuntu: package `libmagickwand` too old, see `https://github.com/SoftCreatR/imei` 41 | * Other: https://imagemagick.org/script/download.php 42 | 43 | Before installing the vim plugin, make sure that toolchains for Rust and C are installed: 44 | 45 | * for C install `make` and `gcc` 46 | * for Rust install a stable toolchain, for example with [rustup](https://rustup.rs/) 47 | 48 | Finally add the following section to your vim configuration 49 | ``` 50 | Plug 'mattn/libcallex-vim', { 'do': 'make -C autoload' } 51 | Plug 'bytesnake/vim-graphical-preview', { 'do': 'cargo build --release' } 52 | ``` 53 | 54 | and install with `source %|PlugInstall`. 55 | 56 | The plugin is currently not mapped to a file format, but can be manually enabled by setting the `filetype` variable to `graphical-preview` or add the follow preamble to your file: 57 | 58 | ```vim 59 | vim: set filetype=markdown.graphics : 60 | ``` 61 | 62 | ## FAQ 63 | 64 | > The graphic is overlapping with the command and status line 65 | 66 | This is a limitation of SIXEL as it always scrolls after a line and would do that as well for the last one. Otherwise we could overlap and delete with a raster sequence. There is special mode, disabling this behaviour, but then your image is fixed at the upper, left corner (see [here](https://gitlab.com/AutumnMeowMeow/jexer/-/issues/61)). 67 | 68 | > TODO 69 | 70 | ## Kudos to 71 | 72 | - [heapslip](https://github.com/heapslip) for inspiring me with [vimimage](https://www.youtube.com/watch?v=cnt9mPOjrLg) 73 | - the awesome people at alacritty, [Ayose Cazorla](https://github.com/alacritty/alacritty/pull/4763) and [microo8](https://github.com/microo8/alacritty-sixel) 74 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::{str, usize, io::Write}; 3 | use std::path::{Path, PathBuf}; 4 | use std::fs::File; 5 | use std::process::{Command, Stdio}; 6 | use sha2::{Digest, Sha256}; 7 | use nix::{ioctl_read_bad, pty::Winsize}; 8 | 9 | use crate::error::{Error, Result}; 10 | use crate::render::ART_PATH; 11 | 12 | pub fn hash(input: &str) -> String { 13 | let mut hasher = Sha256::new(); 14 | hasher.update(input.as_bytes()); 15 | let result = hasher.finalize(); 16 | let mut x = format!("{:x}", &result); 17 | x.truncate(24); 18 | x 19 | } 20 | 21 | /// Get pixel height of a character 22 | pub fn char_pixel_height() -> usize { 23 | ioctl_read_bad! { tiocgwinsz, 21523, Winsize } 24 | 25 | let mut size = Winsize { 26 | ws_row: 0, 27 | ws_col: 0, 28 | ws_xpixel: 0, 29 | ws_ypixel: 0 30 | }; 31 | 32 | unsafe {tiocgwinsz(0, &mut size).unwrap() }; 33 | 34 | if size.ws_ypixel > 2 { 35 | size.ws_ypixel as usize / size.ws_row as usize 36 | } else { 37 | 28 38 | } 39 | } 40 | 41 | /// Generate SVG file from latex file with given zoom 42 | pub fn generate_svg_from_latex(path: &Path, zoom: f32) -> Result { 43 | let dest_path = path.parent().unwrap(); 44 | let file: &Path = path.file_name().unwrap().as_ref(); 45 | 46 | // use latex to generate a dvi 47 | let dvi_path = path.with_extension("dvi"); 48 | if !dvi_path.exists() { 49 | let latex_path = which::which("latex") 50 | .map_err(Error::BinaryNotFound)?; 51 | 52 | let cmd = Command::new(latex_path) 53 | .current_dir(&dest_path) 54 | //.arg("--jobname").arg(&dvi_path) 55 | .arg(&file.with_extension("tex")) 56 | .output() 57 | .expect("Could not spawn latex"); 58 | 59 | if !cmd.status.success() { 60 | let buf = String::from_utf8_lossy(&cmd.stdout); 61 | 62 | // latex prints error to the stdout, if this is empty, then something is fundamentally 63 | // wrong with the latex binary (for example shared library error). In this case just 64 | // exit the program 65 | if buf.is_empty() { 66 | let buf = String::from_utf8_lossy(&cmd.stderr); 67 | panic!("Latex exited with `{}`", buf); 68 | } 69 | 70 | let err = buf 71 | .split('\n') 72 | .filter(|x| { 73 | (x.starts_with("! ") || x.starts_with("l.")) && !x.contains("Emergency stop") 74 | }) 75 | .fold(("", "", usize::MAX), |mut err, elm| { 76 | if elm.starts_with("! ") { 77 | err.0 = elm; 78 | } else if let Some(elms) = elm.strip_prefix("1.") { 79 | let mut elms = elms.splitn(2, ' ').map(|x| x.trim()); 80 | if let Some(Ok(val)) = elms.next().map(|x| x.parse::()) { 81 | err.2 = val; 82 | } 83 | if let Some(val) = elms.next() { 84 | err.1 = val; 85 | } 86 | } 87 | 88 | err 89 | }); 90 | 91 | return Err(Error::InvalidMath( 92 | err.0.to_string(), 93 | err.1.to_string(), 94 | err.2, 95 | )); 96 | } 97 | } 98 | 99 | // convert the dvi to a svg file with the woff font format 100 | let svg_path = path.with_extension("svg"); 101 | if !svg_path.exists() && dvi_path.exists() { 102 | let dvisvgm_path = which::which("dvisvgm") 103 | .map_err(Error::BinaryNotFound)?; 104 | 105 | let cmd = Command::new(dvisvgm_path) 106 | .current_dir(&dest_path) 107 | .arg("-b") 108 | .arg("1") 109 | //.arg("--font-format=woff") 110 | .arg("--no-fonts") 111 | .arg(&format!("--zoom={}", zoom)) 112 | .arg(&dvi_path) 113 | .output() 114 | .expect("Couldn't run svisvgm properly!"); 115 | 116 | let buf = String::from_utf8_lossy(&cmd.stderr); 117 | if !cmd.status.success() || buf.contains("error:") { 118 | return Err(Error::InvalidDvisvgm(buf.to_string())); 119 | } 120 | } 121 | 122 | Ok(path.to_path_buf()) 123 | } 124 | 125 | /// Parse an equation with the given zoom 126 | pub fn parse_equation( 127 | content: &str, 128 | zoom: f32, 129 | ) -> Result { 130 | let path = Path::new(ART_PATH).join(hash(content)).with_extension("svg"); 131 | 132 | // create a new tex file containing the equation 133 | if !path.with_extension("tex").exists() { 134 | let mut file = File::create(path.with_extension("tex")).map_err(Error::Io)?; 135 | 136 | file.write_all("\\documentclass[20pt, preview]{standalone}\n\\usepackage{amsmath}\\usepackage{amsfonts}\n\\begin{document}\n$$\n".as_bytes()) 137 | .map_err(Error::Io)?; 138 | 139 | file.write_all(content.as_bytes()) 140 | .map_err(Error::Io)?; 141 | 142 | file.write_all("$$\n\\end{document}".as_bytes()) 143 | .map_err(Error::Io)?; 144 | } 145 | 146 | generate_svg_from_latex(&path, zoom) 147 | } 148 | 149 | /// Generate latex file from gnuplot 150 | /// 151 | /// This function generates a latex file with gnuplot `epslatex` backend and then source it into 152 | /// the generate latex function 153 | pub fn generate_latex_from_gnuplot(content: &str) -> Result { 154 | let path = Path::new(ART_PATH).join(hash(content)).with_extension("tex"); 155 | 156 | let gnuplot_path = which::which("gnuplot") 157 | .map_err(Error::BinaryNotFound)?; 158 | 159 | let cmd = Command::new(gnuplot_path) 160 | .stdin(Stdio::piped()) 161 | .current_dir(ART_PATH) 162 | .arg("-p") 163 | .spawn() 164 | .unwrap(); 165 | //.expect("Could not spawn gnuplot"); 166 | 167 | let mut stdin = cmd.stdin.unwrap(); 168 | 169 | stdin 170 | .write_all(format!("set output '{}'\n", path.file_name().unwrap().to_str().unwrap()).as_bytes()) 171 | .map_err(Error::Io)?; 172 | stdin 173 | .write_all("set terminal epslatex color standalone\n".as_bytes()) 174 | .map_err(Error::Io)?; 175 | stdin 176 | .write_all(content.as_bytes()) 177 | .map_err(Error::Io)?; 178 | 179 | Ok(path) 180 | } 181 | 182 | pub fn generate_latex_from_gnuplot_file(path: &Path) -> Result { 183 | let mut content = String::new(); 184 | let mut f = File::open(path) 185 | .map_err(Error::Io)?; 186 | f.read_to_string(&mut content).unwrap(); 187 | 188 | let path = generate_latex_from_gnuplot(&content)?; 189 | generate_svg_from_latex(&path, 1.0) 190 | } 191 | 192 | /// Parse a latex content and convert it to a SVG file 193 | pub fn parse_latex( 194 | content: &str, 195 | ) -> Result { 196 | let path = Path::new(ART_PATH).join(hash(content)).with_extension("svg"); 197 | 198 | // create a new tex file containing the equation 199 | if !path.with_extension("tex").exists() { 200 | let mut file = File::create(&path.with_extension("tex")).map_err(Error::Io)?; 201 | 202 | file.write_all(content.as_bytes()) 203 | .map_err(Error::Io)?; 204 | } 205 | 206 | if !path.exists() { 207 | generate_svg_from_latex(&path, 1.0)?; 208 | } 209 | 210 | Ok(path) 211 | } 212 | 213 | pub fn parse_latex_from_file( 214 | path: &Path, 215 | ) -> Result { 216 | let mut content = String::new(); 217 | let mut f = File::open(path) 218 | .map_err(Error::Io)?; 219 | f.read_to_string(&mut content).unwrap(); 220 | 221 | parse_latex(&content) 222 | } 223 | -------------------------------------------------------------------------------- /src/content.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::path::PathBuf; 3 | use std::collections::{BTreeMap, HashMap}; 4 | use std::thread; 5 | use std::sync::{RwLock, Arc}; 6 | use magick_rust::MagickWand; 7 | 8 | use crate::error::{Error, Result}; 9 | use crate::render::{FoldState, Fold, FoldInner, ART_PATH, CodeId}; 10 | use crate::node_view::NodeView; 11 | use crate::utils; 12 | 13 | pub type Sixel = Vec; 14 | 15 | #[derive(PartialEq, Eq, Hash, Debug, Clone)] 16 | pub struct NodeDim { 17 | pub(crate) height: usize, 18 | pub(crate) crop: Option<(usize, usize)>, 19 | } 20 | 21 | #[derive(Debug, Eq, PartialEq, Clone)] 22 | pub enum ContentType { 23 | Math, 24 | Gnuplot, 25 | Tex, 26 | File, 27 | } 28 | 29 | impl ContentType { 30 | pub fn from_fence(kind: &str) -> Result { 31 | match kind { 32 | "math" => Ok(Self::Math), 33 | "gnuplot" => Ok(Self::Gnuplot), 34 | "latex" | "tex" => Ok(Self::Tex), 35 | _ => Err(Error::UnknownFence(kind.to_string())), 36 | } 37 | } 38 | 39 | pub fn generate(&self, content: String) -> Result { 40 | let mut path = self.path(&content); 41 | let missing = !path.exists(); 42 | 43 | if missing { 44 | match self { 45 | ContentType::Math => { 46 | utils::parse_equation(&content, 1.0)?; 47 | }, 48 | ContentType::File => { 49 | return Err(Error::FileNotFound(path)) 50 | }, 51 | ContentType::Tex => { 52 | utils::parse_latex(&content)?; 53 | }, 54 | ContentType::Gnuplot => { 55 | let path = utils::generate_latex_from_gnuplot(&content)?; 56 | utils::generate_svg_from_latex(&path, 1.0)?; 57 | }, 58 | } 59 | } 60 | 61 | // rewrite path if ending as tex or gnuplot file 62 | if *self == ContentType::File { 63 | if path.extension().unwrap() == "tex" { 64 | path = utils::parse_latex_from_file(&path)?; 65 | } 66 | 67 | if path.extension().unwrap() == "plt" { 68 | let new_path = utils::generate_latex_from_gnuplot_file(&path)?; 69 | path = new_path.with_extension("svg"); 70 | } 71 | } 72 | 73 | let wand = MagickWand::new(); 74 | wand.set_resolution(600.0, 600.0).unwrap(); 75 | 76 | wand.read_image(path.to_str().unwrap()) 77 | .map_err(|_| Error::InvalidImage(path.to_str().unwrap().to_string()))?; 78 | 79 | //wand.set_compression_quality(5).unwrap(); 80 | //wand.transform_image_colorspace(ColorspaceType_GRAYColorspace).unwrap(); 81 | //wand.quantize_image(8, ColorspaceType_GRAYColorspace, 0, DitherMethod_NoDitherMethod, 0).unwrap(); 82 | 83 | Ok(WrappedWand(wand)) 84 | } 85 | 86 | pub fn path(&self, content: &str) -> PathBuf { 87 | let id = utils::hash(content); 88 | match self { 89 | ContentType::File => PathBuf::from(content), 90 | _ => PathBuf::from(ART_PATH).join(id).with_extension("svg"), 91 | } 92 | } 93 | } 94 | 95 | #[derive(Clone)] 96 | pub struct WrappedWand(MagickWand); 97 | 98 | impl WrappedWand { 99 | pub fn wand_to_sixel(self, dim: NodeDim) -> Vec { 100 | self.0.fit(100000, dim.height); 101 | 102 | if let Some(crop) = dim.crop { 103 | self.0.crop_image(self.0.get_image_width(), crop.0, 0, crop.1 as isize).unwrap(); 104 | } 105 | 106 | self.0.write_image_blob("sixel").unwrap() 107 | } 108 | } 109 | 110 | unsafe impl Send for WrappedWand {} 111 | unsafe impl Sync for WrappedWand {} 112 | 113 | pub enum ContentState { 114 | Empty, 115 | Running, 116 | Ok(WrappedWand), 117 | Err(Error), 118 | } 119 | 120 | impl ContentState { 121 | pub fn new() -> Shared { 122 | Arc::new(RwLock::new(ContentState::Empty)) 123 | } 124 | } 125 | 126 | 127 | type Shared = Arc>; 128 | 129 | pub struct Node { 130 | pub id: CodeId, 131 | pub range: (usize, usize), 132 | content: (String, ContentType), 133 | state: Shared, 134 | sixel_cache: Shared>, 135 | } 136 | 137 | impl Node { 138 | pub fn new(id: CodeId, range: (usize, usize), content: &str, kind: ContentType) -> Node { 139 | let state = ContentState::new(); 140 | let sixel_cache = Arc::new(RwLock::new(HashMap::new())); 141 | let content = (content.to_string(), kind); 142 | 143 | Node { 144 | id, range, state, sixel_cache, content 145 | } 146 | } 147 | 148 | pub fn get_sixel(&mut self, dim: NodeDim) -> Option> { 149 | let Node { sixel_cache, state, content, .. } = self; 150 | 151 | // first check the SIXEL blob cache 152 | if let Some(data) = (*sixel_cache.read().unwrap()).get(&dim) { 153 | return Some(Ok(data.clone())); 154 | } 155 | 156 | let state_cont = std::mem::replace(&mut *state.write().unwrap(), ContentState::Empty); 157 | 158 | let (res, state_cont) = match state_cont { 159 | ContentState::Empty => { 160 | let state_cloned = state.clone(); 161 | let content = content.clone(); 162 | thread::spawn(move || { 163 | let res = content.1.generate(content.0); 164 | 165 | *state_cloned.write().unwrap() = match res { 166 | Ok(res) => ContentState::Ok(res), 167 | Err(err) => ContentState::Err(err), 168 | }; 169 | }); 170 | 171 | (None, ContentState::Running) 172 | }, 173 | ContentState::Err(error) => 174 | (Some(Err(error)), ContentState::Empty), 175 | ContentState::Ok(content) => { 176 | // start thread to calculate SIXEL blob 177 | let sixel_cache = sixel_cache.clone(); 178 | let state = state.clone(); 179 | 180 | thread::spawn(move || { 181 | let res = content.clone().wand_to_sixel(dim.clone()); 182 | sixel_cache.write().unwrap().insert(dim, res); 183 | *state.write().unwrap() = ContentState::Ok(content); 184 | }); 185 | 186 | (None, ContentState::Running) 187 | }, 188 | ContentState::Running => (None, ContentState::Running), 189 | }; 190 | 191 | let _ = std::mem::replace(&mut *state.write().unwrap(), state_cont); 192 | 193 | res 194 | } 195 | } 196 | 197 | pub struct Content { 198 | fences_regex: Regex, 199 | file_regex: Regex, 200 | header_regex: Regex, 201 | newlines: Regex, 202 | } 203 | 204 | impl Content { 205 | pub fn new() -> Content { 206 | Content { 207 | fences_regex: Regex::new(r"```(?P([a-z]{3,}))(,height=(?P([\d]+)))?[\w]*\n(?P[\s\S]+?)?```").unwrap(), 208 | file_regex: Regex::new(r#"\n(?P!\[[^\]]*\])\((?P.*?)\)(?P\n*)"#).unwrap(), 209 | header_regex: Regex::new(r"\n(#{1,6}.*)").unwrap(), 210 | newlines: Regex::new(r"\n").unwrap(), 211 | } 212 | } 213 | 214 | pub fn process(&self, content: &str, mut old_nodes: BTreeMap) -> Result<(BTreeMap, BTreeMap, Vec, bool)> { 215 | // put new lines into a btree map for later 216 | let (_, mut new_lines) = self.newlines.find_iter(content) 217 | .map(|x| x.start()) 218 | .fold((1, BTreeMap::new()), |(mut nr, mut map): (usize, BTreeMap), idx| { 219 | nr += 1; 220 | map.insert(idx, nr); 221 | 222 | (nr, map) 223 | }); 224 | new_lines.insert(1, 1); 225 | 226 | let folds = self.header_regex.find_iter(content) 227 | .filter_map(|x| new_lines.get(&x.start())) 228 | .copied() 229 | .collect::>(); 230 | 231 | let mut nodes = BTreeMap::new(); 232 | let mut any_changed = false; 233 | 234 | let maths = self.fences_regex.captures_iter(content) 235 | .map(|x| { 236 | let kind = x.name("name").unwrap().as_str(); 237 | let content = x.name("inner").map_or("", |x| x.as_str()).to_string(); 238 | let height = x.name("height") 239 | .and_then(|x| x.as_str().parse::().ok()) 240 | .unwrap_or_else(|| content.matches('\n').count() + 1); 241 | let line = new_lines.get(&(x.get(0).unwrap().start() - 1)).unwrap(); 242 | let id = utils::hash(&content); 243 | 244 | ContentType::from_fence(kind).map(|c| 245 | (height, *line, content, id, c) 246 | ) 247 | }); 248 | 249 | let files = self.file_regex.captures_iter(content) 250 | .map(|x| { 251 | let file_name = x.name("file_name").unwrap().as_str().to_string(); 252 | let height = x.name("new_lines").unwrap().as_str().len() - 1; 253 | let line = new_lines.get(&x.get(0).unwrap().start()).unwrap() + 1; 254 | let id = utils::hash(&file_name); 255 | 256 | Ok((height, line, file_name, id, ContentType::File)) 257 | }); 258 | 259 | 260 | let strcts_gen = maths.chain(files) 261 | .map(|x| x.map(|(height, line, content, id, kind)| { 262 | let new_range = (line, line + height); 263 | 264 | // try to load from existing structures 265 | if let Some(mut node) = old_nodes.remove(&id) { 266 | if new_range != node.range { 267 | any_changed = true; 268 | } 269 | node.range = new_range; 270 | 271 | nodes.insert(id.clone(), node); 272 | } else { 273 | any_changed = true; 274 | 275 | nodes.insert(id.clone(), Node::new(id.clone(), new_range, &content, kind)); 276 | } 277 | 278 | (line, FoldInner::Node((id, NodeView::Hidden))) 279 | })); 280 | 281 | let strcts = folds.iter() 282 | .map(|line| { 283 | let new_fold = Fold { 284 | state: FoldState::Open, 285 | line: *line, 286 | }; 287 | Ok((*line, FoldInner::Fold(new_fold))) 288 | }) 289 | .chain(strcts_gen) 290 | .collect::>>()?; 291 | 292 | //dbg!(&strcts); 293 | 294 | Ok((nodes, strcts, folds, any_changed)) 295 | } 296 | 297 | } 298 | 299 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Write, Stdout}; 2 | use std::collections::BTreeMap; 3 | use std::path::Path; 4 | use std::fs::File; 5 | use std::os::unix::io::FromRawFd; 6 | use std::mem; 7 | 8 | use miniserde::{json, Serialize, Deserialize}; 9 | 10 | use crate::error::Result; 11 | use crate::utils; 12 | use crate::node_view::NodeView; 13 | use crate::content::{Content, Node, NodeDim}; 14 | 15 | pub const ART_PATH: &str = "/tmp/nvim_arts/"; 16 | 17 | pub type CodeId = String; 18 | pub type Folds = Vec<(usize, isize)>; 19 | 20 | #[derive(Debug, Deserialize)] 21 | pub struct Metadata { 22 | pub file_range: (u64, u64), 23 | pub viewport: (u64, u64), 24 | pub cursor: u64, 25 | pub winpos: (usize, usize), 26 | pub char_height: usize, 27 | } 28 | 29 | impl Metadata { 30 | pub fn new() -> Metadata { 31 | Metadata { 32 | file_range: (1, 1), 33 | viewport: (1, 1), 34 | cursor: 1, 35 | winpos: (1, 1), 36 | char_height: 0, 37 | } 38 | } 39 | } 40 | 41 | #[derive(PartialEq, Eq, Clone, Debug)] 42 | pub enum FoldState { 43 | Folded(usize), 44 | Open, 45 | } 46 | 47 | #[derive(Debug)] 48 | pub struct Fold { 49 | pub line: usize, 50 | pub state: FoldState, 51 | } 52 | 53 | #[derive(Debug)] 54 | pub enum FoldInner { 55 | Fold(Fold), 56 | Node((CodeId, NodeView)), 57 | } 58 | 59 | impl FoldInner { 60 | pub fn is_in_view(&self, metadata: &Metadata, blocks: &BTreeMap) -> bool { 61 | match self { 62 | FoldInner::Node((id, _)) => { 63 | let range = blocks.get(id).unwrap().range; 64 | 65 | range.1 as u64 >= metadata.file_range.0 && 66 | range.0 as u64 <= metadata.file_range.1 67 | }, 68 | FoldInner::Fold(ref fold) => 69 | fold.line as u64 >= metadata.file_range.0 && 70 | fold.line as u64 <= metadata.file_range.1 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug, Serialize)] 76 | pub struct RedrawState { 77 | should_redraw: bool, 78 | update_folding: Option>, 79 | } 80 | 81 | pub struct Render { 82 | stdout: Stdout, 83 | blocks: BTreeMap, 84 | strcts: BTreeMap, 85 | metadata: Metadata, 86 | content: Content, 87 | } 88 | 89 | impl Render { 90 | pub fn new() -> Render { 91 | if !Path::new(ART_PATH).exists() { 92 | std::fs::create_dir(ART_PATH).unwrap(); 93 | } 94 | 95 | Render { 96 | stdout: std::io::stdout(), 97 | blocks: BTreeMap::new(), 98 | strcts: BTreeMap::new(), 99 | metadata: Metadata::new(), 100 | content: Content::new(), 101 | } 102 | } 103 | 104 | pub fn draw(&mut self, _: &str) -> Result { 105 | let mut pending = false; 106 | 107 | // mutable iterator of items, skipping things outside the viewport 108 | let mut items = self.strcts.iter_mut() 109 | .map(|(a, item)| { 110 | if !item.is_in_view(&self.metadata, &self.blocks) { 111 | if let FoldInner::Node((_, ref mut view)) = item { 112 | *view = NodeView::Hidden; 113 | } 114 | } 115 | 116 | (a, item) 117 | }) 118 | .filter(|(_, item)| { 119 | item.is_in_view(&self.metadata, &self.blocks) 120 | }) 121 | .collect::>(); 122 | 123 | // initialize current item 124 | let mut iter = items.iter_mut(); 125 | let mut item = match iter.next() { 126 | Some(x) => x, 127 | None => return Ok(0) 128 | }; 129 | 130 | // initialize last line and top offset, so that first iteration gives offset to first item 131 | let mut last_line = self.metadata.file_range.0 as usize; 132 | let mut top_offset: isize = 0; 133 | 134 | // perform fold skipping if folded in 135 | let mut skip_to = None; 136 | 'outer: loop { 137 | match item.1 { 138 | FoldInner::Node((id, ref mut node_view)) => { 139 | let node = self.blocks.get_mut(id).unwrap(); 140 | 141 | // calculate new offset (this can be negative at the beginning) 142 | top_offset += node.range.0 as isize - last_line as isize; 143 | last_line = node.range.0; 144 | 145 | pending |= Render::draw_node(&self.metadata, &self.stdout, node, node_view, top_offset)?; 146 | }, 147 | FoldInner::Fold(ref fold) => { 148 | // offset has a header of single line 149 | top_offset += fold.line as isize - last_line as isize; 150 | 151 | if let FoldState::Folded(end) = fold.state { 152 | skip_to = Some(end); 153 | 154 | last_line = end; 155 | } else { 156 | last_line = fold.line; 157 | } 158 | } 159 | } 160 | 161 | // get new items and skip until line is reached 162 | loop { 163 | item = match iter.next() { 164 | Some(x) => x, 165 | None => break 'outer 166 | }; 167 | 168 | if let Some(skip_line) = skip_to.take() { 169 | if *item.0 <= skip_line { 170 | skip_to = Some(skip_line); 171 | continue; 172 | } 173 | } 174 | 175 | break; 176 | } 177 | 178 | } 179 | 180 | //dbg!(&pending); 181 | 182 | Ok(if pending { 1 } else { 0 }) 183 | } 184 | pub fn draw_node(metadata: &Metadata, stdout: &Stdout, node: &mut Node, view: &mut NodeView, top_offset: isize) -> Result { 185 | // calculate new view and height of node 186 | let new_view = NodeView::new(node, metadata, top_offset); 187 | let char_height = metadata.char_height; 188 | let theight = node.range.1 - node.range.0; 189 | 190 | let (pos, crop) = match (&view, &new_view) { 191 | (NodeView::UpperBorder(_, _) | NodeView::LowerBorder(_, _) | NodeView::Hidden, NodeView::Visible(pos, _)) => 192 | (*pos, None), 193 | (NodeView::Hidden, NodeView::LowerBorder(pos, height)) => 194 | (*pos, Some((height * char_height, 0))), 195 | (NodeView::LowerBorder(_, height_old), NodeView::LowerBorder(pos, height)) if height_old < height => 196 | (*pos, Some((height * char_height, 0))), 197 | (NodeView::Hidden, NodeView::UpperBorder(y, height)) => 198 | (0, Some((height * char_height, y * char_height))), 199 | (NodeView::UpperBorder(y_old, _), NodeView::UpperBorder(y, height)) if y < y_old => 200 | (0, Some((height * char_height, y * char_height))), 201 | _ => return Ok(false), 202 | }; 203 | 204 | let dim = NodeDim { 205 | height: theight * char_height, 206 | crop 207 | }; 208 | 209 | if let Some(buf) = node.get_sixel(dim) { 210 | // bail out if an error happened during conversion 211 | let mut buf = buf?; 212 | 213 | //dbg!(&metadata.winpos.0, &metadata.winpos.1); 214 | let mut wbuf = format!("\x1b[s\x1b[{};{}H", pos + metadata.winpos.0, metadata.winpos.1).into_bytes(); 215 | //for _ in 0..(node.range.1-node.range.0 - 1) { 216 | // wbuf.extend_from_slice(b"\x1b[B\x1b[K"); 217 | //} 218 | 219 | //wbuf.append(&mut format!("\x1b[{};{}H", pos + metadata.winpos.0, metadata.winpos.1).into_bytes()); 220 | //dbg!(&buf.len()); 221 | wbuf.append(&mut buf); 222 | //wbuf.append(&mut format!("\x1b[{};{}H", metadata.viewport.0, metadata.winpos.1).into_bytes()); 223 | //wbuf.append(&mut format!("\x1b[?80h\x1bP100;1q\"1;1;2000;50\"1;1;2000;50\x1b[u\x1b\\").into_bytes()); 224 | //wbuf.extend_from_slice(b"\x1b[u"); 225 | wbuf.extend_from_slice(b"\x1b[u"); 226 | 227 | { 228 | let outer_lock = stdout.lock(); 229 | let mut stdout = unsafe { File::from_raw_fd(1) }; 230 | let mut idx = 0; 231 | while idx < wbuf.len() { 232 | match stdout.write(&wbuf[idx..]) { 233 | Ok(n) => idx += n, 234 | Err(_) => {/*eprintln!("{}", err);*/}, 235 | } 236 | } 237 | std::mem::forget(stdout); 238 | drop(outer_lock); 239 | } 240 | 241 | Ok(false) 242 | } else { 243 | Ok(new_view.is_visible()) 244 | } 245 | } 246 | 247 | pub fn clear_all(&mut self, _: &str) -> Result<()> { 248 | for fold in self.strcts.values_mut() { 249 | if let FoldInner::Node(ref mut node) = fold { 250 | node.1 = NodeView::Hidden; 251 | } 252 | } 253 | 254 | Ok(()) 255 | } 256 | 257 | pub fn update_metadata(&mut self, metadata: &str) -> Result<()> { 258 | let mut metadata: Metadata = json::from_str(metadata).unwrap(); 259 | metadata.char_height = utils::char_pixel_height(); 260 | 261 | let rerender = metadata.viewport != self.metadata.viewport; 262 | if rerender { 263 | self.clear_all("")?; 264 | } 265 | 266 | self.metadata = metadata; 267 | 268 | Ok(()) 269 | } 270 | 271 | pub fn update_content(&mut self, content: &str) -> Result { 272 | let old_blocks = mem::take(&mut self.blocks); 273 | let (nodes, strcts, folds, any_changed) = self.content.process(content, old_blocks)?; 274 | 275 | self.strcts = strcts; 276 | self.blocks = nodes; 277 | 278 | let ret = RedrawState { 279 | should_redraw: any_changed, 280 | update_folding: Some(folds), 281 | }; 282 | 283 | Ok(json::to_string(&ret)) 284 | } 285 | 286 | pub fn set_folds(&mut self, folds: &str) -> Result { 287 | let folds: Folds = json::from_str(folds).unwrap(); 288 | let mut folds = folds.into_iter(); 289 | 290 | let mut any_changed = false; 291 | 292 | // loop through structs and update fold information 293 | let mut end_fold: Option = None; 294 | for (line, elm) in &mut self.strcts { 295 | if let Some(tmp) = &end_fold { 296 | if tmp < line { 297 | end_fold = None; 298 | } 299 | } 300 | 301 | match elm { 302 | FoldInner::Fold(ref mut fold) => { 303 | let (start, end) = folds.next().unwrap(); 304 | assert!(*line == start); 305 | 306 | let prev = fold.state.clone(); 307 | 308 | if end == -1 { 309 | fold.state = FoldState::Open; 310 | } else { 311 | fold.state = FoldState::Folded(end as usize); 312 | 313 | if prev == FoldState::Open { 314 | end_fold = Some(end as usize); 315 | } 316 | } 317 | 318 | if prev != fold.state { 319 | any_changed = true; 320 | } 321 | }, 322 | FoldInner::Node((_, ref mut view)) => { 323 | if let Some(tmp) = &end_fold { 324 | if line < tmp { 325 | *view = NodeView::Hidden; 326 | } 327 | } 328 | } 329 | } 330 | } 331 | 332 | Ok(if any_changed { 1 } else { 0 }) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 39 | 40 | [[package]] 41 | name = "bindgen" 42 | version = "0.59.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" 45 | dependencies = [ 46 | "bitflags", 47 | "cexpr", 48 | "clang-sys", 49 | "clap", 50 | "env_logger", 51 | "lazy_static 1.4.0", 52 | "lazycell", 53 | "log", 54 | "peeking_take_while", 55 | "proc-macro2", 56 | "quote", 57 | "regex", 58 | "rustc-hash", 59 | "shlex", 60 | "which", 61 | ] 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "1.3.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 68 | 69 | [[package]] 70 | name = "block-buffer" 71 | version = "0.10.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 74 | dependencies = [ 75 | "generic-array", 76 | ] 77 | 78 | [[package]] 79 | name = "cc" 80 | version = "1.0.73" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 83 | 84 | [[package]] 85 | name = "cexpr" 86 | version = "0.6.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 89 | dependencies = [ 90 | "nom", 91 | ] 92 | 93 | [[package]] 94 | name = "cfg-if" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 98 | 99 | [[package]] 100 | name = "clang-sys" 101 | version = "1.3.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" 104 | dependencies = [ 105 | "glob", 106 | "libc", 107 | "libloading", 108 | ] 109 | 110 | [[package]] 111 | name = "clap" 112 | version = "2.34.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 115 | dependencies = [ 116 | "ansi_term", 117 | "atty", 118 | "bitflags", 119 | "strsim", 120 | "textwrap", 121 | "unicode-width", 122 | "vec_map", 123 | ] 124 | 125 | [[package]] 126 | name = "cpufeatures" 127 | version = "0.2.4" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" 130 | dependencies = [ 131 | "libc", 132 | ] 133 | 134 | [[package]] 135 | name = "crypto-common" 136 | version = "0.1.6" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 139 | dependencies = [ 140 | "generic-array", 141 | "typenum", 142 | ] 143 | 144 | [[package]] 145 | name = "digest" 146 | version = "0.10.3" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 149 | dependencies = [ 150 | "block-buffer", 151 | "crypto-common", 152 | ] 153 | 154 | [[package]] 155 | name = "either" 156 | version = "1.8.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 159 | 160 | [[package]] 161 | name = "env_logger" 162 | version = "0.9.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 165 | dependencies = [ 166 | "atty", 167 | "humantime", 168 | "log", 169 | "regex", 170 | "termcolor", 171 | ] 172 | 173 | [[package]] 174 | name = "flame" 175 | version = "0.2.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "1fc2706461e1ee94f55cab2ed2e3d34ae9536cfa830358ef80acff1a3dacab30" 178 | dependencies = [ 179 | "lazy_static 0.2.11", 180 | "serde", 181 | "serde_derive", 182 | "serde_json", 183 | "thread-id", 184 | ] 185 | 186 | [[package]] 187 | name = "generic-array" 188 | version = "0.14.6" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 191 | dependencies = [ 192 | "typenum", 193 | "version_check", 194 | ] 195 | 196 | [[package]] 197 | name = "glob" 198 | version = "0.3.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 201 | 202 | [[package]] 203 | name = "hermit-abi" 204 | version = "0.1.19" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 207 | dependencies = [ 208 | "libc", 209 | ] 210 | 211 | [[package]] 212 | name = "humantime" 213 | version = "2.1.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 216 | 217 | [[package]] 218 | name = "itoa" 219 | version = "1.0.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 222 | 223 | [[package]] 224 | name = "lazy_static" 225 | version = "0.2.11" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 228 | 229 | [[package]] 230 | name = "lazy_static" 231 | version = "1.4.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 234 | 235 | [[package]] 236 | name = "lazycell" 237 | version = "1.3.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 240 | 241 | [[package]] 242 | name = "libc" 243 | version = "0.2.132" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 246 | 247 | [[package]] 248 | name = "libloading" 249 | version = "0.7.3" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 252 | dependencies = [ 253 | "cfg-if", 254 | "winapi", 255 | ] 256 | 257 | [[package]] 258 | name = "log" 259 | version = "0.4.17" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 262 | dependencies = [ 263 | "cfg-if", 264 | ] 265 | 266 | [[package]] 267 | name = "magick_rust" 268 | version = "0.15.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "13584c9d754ca069ee43d1ee155c4a14a87e7e68faf7b61255f2d3133ee259c4" 271 | dependencies = [ 272 | "bindgen", 273 | "libc", 274 | "pkg-config", 275 | ] 276 | 277 | [[package]] 278 | name = "memchr" 279 | version = "2.5.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 282 | 283 | [[package]] 284 | name = "memoffset" 285 | version = "0.6.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 288 | dependencies = [ 289 | "autocfg", 290 | ] 291 | 292 | [[package]] 293 | name = "mini-internal" 294 | version = "0.1.27" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "9a63337614a1d280fdb2880599af563c99e9f388757f8d6515d785d85d14fb01" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "syn", 301 | ] 302 | 303 | [[package]] 304 | name = "minimal-lexical" 305 | version = "0.2.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 308 | 309 | [[package]] 310 | name = "miniserde" 311 | version = "0.1.27" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "5f4313e4a66a442473e181963daf8c1e9def85c2d9fb0bb2ae59444260b28285" 314 | dependencies = [ 315 | "itoa", 316 | "mini-internal", 317 | "ryu", 318 | ] 319 | 320 | [[package]] 321 | name = "nix" 322 | version = "0.23.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" 325 | dependencies = [ 326 | "bitflags", 327 | "cc", 328 | "cfg-if", 329 | "libc", 330 | "memoffset", 331 | ] 332 | 333 | [[package]] 334 | name = "nom" 335 | version = "7.1.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 338 | dependencies = [ 339 | "memchr", 340 | "minimal-lexical", 341 | ] 342 | 343 | [[package]] 344 | name = "peeking_take_while" 345 | version = "0.1.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 348 | 349 | [[package]] 350 | name = "pkg-config" 351 | version = "0.3.25" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 354 | 355 | [[package]] 356 | name = "proc-macro2" 357 | version = "1.0.43" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 360 | dependencies = [ 361 | "unicode-ident", 362 | ] 363 | 364 | [[package]] 365 | name = "quote" 366 | version = "1.0.21" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 369 | dependencies = [ 370 | "proc-macro2", 371 | ] 372 | 373 | [[package]] 374 | name = "redox_syscall" 375 | version = "0.1.57" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 378 | 379 | [[package]] 380 | name = "regex" 381 | version = "1.6.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 384 | dependencies = [ 385 | "aho-corasick", 386 | "memchr", 387 | "regex-syntax", 388 | ] 389 | 390 | [[package]] 391 | name = "regex-syntax" 392 | version = "0.6.27" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 395 | 396 | [[package]] 397 | name = "rustc-hash" 398 | version = "1.1.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 401 | 402 | [[package]] 403 | name = "ryu" 404 | version = "1.0.11" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 407 | 408 | [[package]] 409 | name = "serde" 410 | version = "1.0.144" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" 413 | 414 | [[package]] 415 | name = "serde_derive" 416 | version = "1.0.144" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" 419 | dependencies = [ 420 | "proc-macro2", 421 | "quote", 422 | "syn", 423 | ] 424 | 425 | [[package]] 426 | name = "serde_json" 427 | version = "1.0.85" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 430 | dependencies = [ 431 | "itoa", 432 | "ryu", 433 | "serde", 434 | ] 435 | 436 | [[package]] 437 | name = "sha2" 438 | version = "0.10.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" 441 | dependencies = [ 442 | "cfg-if", 443 | "cpufeatures", 444 | "digest", 445 | ] 446 | 447 | [[package]] 448 | name = "shlex" 449 | version = "1.1.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 452 | 453 | [[package]] 454 | name = "strsim" 455 | version = "0.8.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 458 | 459 | [[package]] 460 | name = "syn" 461 | version = "1.0.99" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 464 | dependencies = [ 465 | "proc-macro2", 466 | "quote", 467 | "unicode-ident", 468 | ] 469 | 470 | [[package]] 471 | name = "termcolor" 472 | version = "1.1.3" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 475 | dependencies = [ 476 | "winapi-util", 477 | ] 478 | 479 | [[package]] 480 | name = "textwrap" 481 | version = "0.11.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 484 | dependencies = [ 485 | "unicode-width", 486 | ] 487 | 488 | [[package]] 489 | name = "thread-id" 490 | version = "3.3.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" 493 | dependencies = [ 494 | "libc", 495 | "redox_syscall", 496 | "winapi", 497 | ] 498 | 499 | [[package]] 500 | name = "typenum" 501 | version = "1.15.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 504 | 505 | [[package]] 506 | name = "unicode-ident" 507 | version = "1.0.3" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 510 | 511 | [[package]] 512 | name = "unicode-width" 513 | version = "0.1.9" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 516 | 517 | [[package]] 518 | name = "vec_map" 519 | version = "0.8.2" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 522 | 523 | [[package]] 524 | name = "version_check" 525 | version = "0.9.4" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 528 | 529 | [[package]] 530 | name = "vim-graphical-preview" 531 | version = "0.1.1" 532 | dependencies = [ 533 | "flame", 534 | "magick_rust", 535 | "miniserde", 536 | "nix", 537 | "regex", 538 | "sha2", 539 | "which", 540 | ] 541 | 542 | [[package]] 543 | name = "which" 544 | version = "4.2.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" 547 | dependencies = [ 548 | "either", 549 | "lazy_static 1.4.0", 550 | "libc", 551 | ] 552 | 553 | [[package]] 554 | name = "winapi" 555 | version = "0.3.9" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 558 | dependencies = [ 559 | "winapi-i686-pc-windows-gnu", 560 | "winapi-x86_64-pc-windows-gnu", 561 | ] 562 | 563 | [[package]] 564 | name = "winapi-i686-pc-windows-gnu" 565 | version = "0.4.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 568 | 569 | [[package]] 570 | name = "winapi-util" 571 | version = "0.1.5" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 574 | dependencies = [ 575 | "winapi", 576 | ] 577 | 578 | [[package]] 579 | name = "winapi-x86_64-pc-windows-gnu" 580 | version = "0.4.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 583 | --------------------------------------------------------------------------------