web-view 0.7.3

Rust bindings for webview, a tiny cross-platform library to render web-based GUIs for desktop applications
extern crate grep;
extern crate walkdir;
extern crate web_view;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tinyfiledialogs as tfd;

use grep::regex::RegexMatcher;
use grep::searcher::sinks::UTF8;
use grep::searcher::{BinaryDetection, SearcherBuilder};
use std::error::Error;
use std::ffi::OsString;
use tfd::MessageBoxIcon;
use walkdir::WalkDir;
use web_view::*;

fn main() {
    web_view::builder()
        .title("Simple Grep Example")
        .content(Content::Html(HTML))
        .size(825, 625)
        .resizable(true)
        .debug(true)
        .user_data(())
        .invoke_handler(|webview, arg| {
            use Cmd::*;

            match serde_json::from_str(arg).unwrap() {
                Search { pattern, path } => {
                    let result = match search(&pattern, OsString::from(path)) {
                        Ok(s) => s,
                        Err(err) => {
                            let err_str = format!("{}", err);
                            tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
                            OsString::from("")
                        }
                    };
                    if result.is_empty() {
                        tfd::message_box_ok(
                            "Information",
                            "No results were found!",
                            MessageBoxIcon::Info,
                        );
                    } else {
                        let eval_str = format!("LoadTextArea({:?});", result);
                        webview.eval(&eval_str)?;
                    }
                }

                Browse {} => match tfd::open_file_dialog("Please choose a file...", "", None) {
                    Some(path_selected) => {
                        let eval_str = format!("SetPath({:?});", path_selected);
                        webview.eval(&eval_str)?;
                    }
                    None => {
                        tfd::message_box_ok(
                            "Warning",
                            "You didn't choose a file.",
                            MessageBoxIcon::Warning,
                        );
                    }
                },

                Error { msg } => tfd::message_box_ok("Error", &msg, MessageBoxIcon::Error),
            }

            Ok(())
        })
        .run()
        .unwrap();
}

#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
    Search { pattern: String, path: String },
    Browse {},
    Error { msg: String },
}

fn search(pattern: &str, path: OsString) -> Result<OsString, Box<dyn Error>> {
    let matcher = RegexMatcher::new_line_matcher(&pattern)?;
    let mut matches: OsString = OsString::new();
    let mut searcher = SearcherBuilder::new()
        .binary_detection(BinaryDetection::quit(b'\x00'))
        .line_number(true)
        .build();

    let mut matched_line = OsString::new();

    for result in WalkDir::new(path) {
        let entry = match result {
            Ok(entry) => entry,
            Err(err) => {
                let err_str = format!("{}", err);
                tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
                continue;
            }
        };
        if !entry.file_type().is_file() {
            continue;
        }

        match searcher.search_path(
            &matcher,
            entry.path(),
            UTF8(|lnum, line| {
                matched_line = OsString::from(format!(
                    "{:?}\t {}:\t {}",
                    entry.path(),
                    lnum.to_string(),
                    line.to_string()
                ));
                matches.push(&matched_line);
                matches.push("\n");
                Ok(true)
            }),
        ) {
            Ok(()) => (),
            Err(err) => {
                let err_str = format!("{}: {:?}", err, entry.path());
                tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
                continue;
            }
        }
    }

    Ok(matches)
}

const HTML: &str = r#"
<!doctype html>
<html>
	<head>
		<style>
			.textarea {
				width: 100%;
				height: 30em;
				font-size: 1em;				
			}
		</style>
		<script type="text/javascript">
			'use strict';
			var rpc = {
				invoke : function(arg) { window.external.invoke(JSON.stringify(arg)); },
				search : function() {
					var pattern = document.getElementById("pattern");
					var path = document.getElementById("path");
					if (pattern.value.trim().length === 0) {
						rpc.error("No pattern entered!");
						return;
					}
					if (path.value.trim().length === 0) {
						rpc.error("No path entered!");
						return;
					}
					var textArea = document.getElementById("text_box");
					textArea.value = "";
					rpc.invoke({cmd : 'search', path : path.value, pattern : pattern.value});
				},
				browse : function() { rpc.invoke({cmd : 'browse'}); },
				error : function(msg) { rpc.invoke({cmd : 'error', msg : msg}); },
			};
				
			function LoadTextArea(data) {
				var textArea = document.getElementById("text_box");
				textArea.value = data;
			}
			function SetPath(path_selected) {
				var path = document.getElementById("path");
				path.value = path_selected;
			}
		</script>
	</head>
	<body>
		<label for="pattern">Pattern to search for:</label>
		<input style="font-size:16px" id="pattern" type="text" size="35" />
		<label style="font-size:14px"> (prefix text with "(?i)" to ignore case)</label><br><br>
		<label for="path">Path (directory or file):</label>
		<input id="path" type="text" size="70" />
	    <button onclick="rpc.browse()">Browse</button>
		<button onclick="rpc.search()">Search</button>
		<textarea class="textarea" id="text_box"></textarea>
	</body>
</html>
"#;