<commit_before>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{cmp, io};
use std::ffi::OsStr;
use std::process::Command;

#[cfg(not(windows))]
use std::os::unix::process::CommandExt;

#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
use std::process::Stdio;
#[cfg(windows)]
use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};

/// Threading utilities
pub mod thread {
    /// Like `thread::spawn`, but with a `name` argument
    pub fn spawn_named<F, T, S>(name: S, f: F) -> ::std::thread::JoinHandle<T>
    where
        F: FnOnce() -> T,
        F: Send + 'static,
        T: Send + 'static,
        S: Into<String>,
    {
        ::std::thread::Builder::new()
            .name(name.into())
            .spawn(f)
            .expect("thread spawn works")
    }

    pub use std::thread::*;
}

pub fn limit<T: Ord>(value: T, min: T, max: T) -> T {
    cmp::min(cmp::max(value, min), max)
}

/// Utilities for writing to the
pub mod fmt {
    use std::fmt;

    macro_rules! define_colors {
        ($($(#[$attrs:meta])* pub struct $s:ident => $color:expr;)*) => {
            $(
                $(#[$attrs])*
                pub struct $s<T>(pub T);

                impl<T: fmt::Display> fmt::Display for $s<T> {
                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        write!(f, concat!("\x1b[", $color, "m{}\x1b[0m"), self.0)
                    }
                }

                impl<T: fmt::Debug> fmt::Debug for $s<T> {
                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        write!(f, concat!("\x1b[", $color, "m{:?}\x1b[0m"), self.0)
                    }
                }
            )*
        }
    }

    define_colors! {
        /// Write a `Display` or `Debug` escaped with Red
        pub struct Red => "31";

        /// Write a `Display` or `Debug` escaped with Green
        pub struct Green => "32";

        /// Write a `Display` or `Debug` escaped with Yellow
        pub struct Yellow => "33";
    }
}

#[cfg(not(windows))]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
{
    Command::new(program)
        .args(args)
        .before_exec(|| unsafe {
            ::libc::daemon(1, 0);
            Ok(())
        })
        .spawn()?
        .wait()
        .map(|_| ())
}

#[cfg(windows)]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
{
    // Setting all the I/O handles to null and setting the
    // CREATE_NEW_PROCESS_GROUP and CREATE_NO_WINDOW has the effect
    // that console applications will run without opening a new
    // console window.
    Command::new(program)
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW)
        .spawn()
        .map(|_| ())
}

#[cfg(test)]
mod tests {
    use super::limit;

    #[test]
    fn limit_works() {
        assert_eq!(10, limit(10, 0, 100));
        assert_eq!(10, limit(5, 10, 100));
        assert_eq!(100, limit(1000, 10, 100));
    }
}
<commit_msg>Remove deprecated libc::daemon call<commit_after>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{cmp, io};
use std::ffi::OsStr;
use std::process::Command;
use std::process::Stdio;

#[cfg(not(windows))]
use std::os::unix::process::CommandExt;

#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};

/// Threading utilities
pub mod thread {
    /// Like `thread::spawn`, but with a `name` argument
    pub fn spawn_named<F, T, S>(name: S, f: F) -> ::std::thread::JoinHandle<T>
    where
        F: FnOnce() -> T,
        F: Send + 'static,
        T: Send + 'static,
        S: Into<String>,
    {
        ::std::thread::Builder::new()
            .name(name.into())
            .spawn(f)
            .expect("thread spawn works")
    }

    pub use std::thread::*;
}

pub fn limit<T: Ord>(value: T, min: T, max: T) -> T {
    cmp::min(cmp::max(value, min), max)
}

/// Utilities for writing to the
pub mod fmt {
    use std::fmt;

    macro_rules! define_colors {
        ($($(#[$attrs:meta])* pub struct $s:ident => $color:expr;)*) => {
            $(
                $(#[$attrs])*
                pub struct $s<T>(pub T);

                impl<T: fmt::Display> fmt::Display for $s<T> {
                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        write!(f, concat!("\x1b[", $color, "m{}\x1b[0m"), self.0)
                    }
                }

                impl<T: fmt::Debug> fmt::Debug for $s<T> {
                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        write!(f, concat!("\x1b[", $color, "m{:?}\x1b[0m"), self.0)
                    }
                }
            )*
        }
    }

    define_colors! {
        /// Write a `Display` or `Debug` escaped with Red
        pub struct Red => "31";

        /// Write a `Display` or `Debug` escaped with Green
        pub struct Green => "32";

        /// Write a `Display` or `Debug` escaped with Yellow
        pub struct Yellow => "33";
    }
}

#[cfg(not(windows))]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
{
    Command::new(program)
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .before_exec(|| unsafe {
            if ::libc::fork() != 0 {
                std::process::exit(0);
            }
            Ok(())
        })
        .spawn()?
        .wait()
        .map(|_| ())
}

#[cfg(windows)]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
{
    // Setting all the I/O handles to null and setting the
    // CREATE_NEW_PROCESS_GROUP and CREATE_NO_WINDOW has the effect
    // that console applications will run without opening a new
    // console window.
    Command::new(program)
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW)
        .spawn()
        .map(|_| ())
}

#[cfg(test)]
mod tests {
    use super::limit;

    #[test]
    fn limit_works() {
        assert_eq!(10, limit(10, 0, 100));
        assert_eq!(10, limit(5, 10, 100));
        assert_eq!(100, limit(1000, 10, 100));
    }
}
<|endoftext|>


<commit_before>//! Convenient helper functions for common use cases:
//! * Printing to terminal
//! * Iterating lines with `\n`s
//! * Modifying ranges of highlighted output

use crate::highlighting::{Style, StyleModifier};
use std::fmt::Write;
use std::ops::Range;
#[cfg(feature = "parsing")]
use crate::parsing::ScopeStackOp;

/// Formats the styled fragments using 24-bit color terminal escape codes.
/// Meant for debugging and testing.
///
/// This function is currently fairly inefficient in its use of escape codes.
///
/// Note that this does not currently ever un-set the color so that the end of a line will also get
/// highlighted with the background.  This means if you might want to use `println!("\x1b[0m");`
/// after to clear the coloring.
///
/// If `bg` is true then the background is also set
pub fn as_24_bit_terminal_escaped(v: &[(Style, &str)], bg: bool) -> String {
    let mut s: String = String::new();
    for &(ref style, text) in v.iter() {
        if bg {
            write!(s,
                   "\x1b[48;2;{};{};{}m",
                   style.background.r,
                   style.background.g,
                   style.background.b)
                .unwrap();
        }
        write!(s,
               "\x1b[38;2;{};{};{}m{}",
               style.foreground.r,
               style.foreground.g,
               style.foreground.b,
               text)
            .unwrap();
    }
    // s.push_str("\x1b[0m");
    s
}

const LATEX_REPLACE: [(&str, &str); 3] = [
    ("\\", "\\\\"),
    ("{", "\\{"),
    ("}", "\\}"),
];

/// Formats the styled fragments using LaTeX textcolor directive.
///
/// Usage is similar to the `as_24_bit_terminal_escaped` function:
///
/// ```
/// use syntect::easy::HighlightLines;
/// use syntect::parsing::SyntaxSet;
/// use syntect::highlighting::{ThemeSet,Style};
/// use syntect::util::{as_latex_escaped,LinesWithEndings};
///
/// // Load these once at the start of your program
/// let ps = SyntaxSet::load_defaults_newlines();
/// let ts = ThemeSet::load_defaults();
///
/// let syntax = ps.find_syntax_by_extension("rs").unwrap();
/// let s = "pub struct Wow { hi: u64 }\nfn blah() -> u64 {}\n";
///
/// let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]);
/// for line in LinesWithEndings::from(s) { // LinesWithEndings enables use of newlines mode
///     let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
///     let escaped = as_latex_escaped(&ranges[..]);
///     println!("{}", escaped);
/// }
/// ```
///
/// Returned content is intended to be placed inside a fancyvrb
/// Verbatim environment:
///
/// ```latex
/// \usepackage{fancyvrb}
/// \usepackage{xcolor}
/// % ...
/// % enable comma-separated arguments inside \textcolor
/// \makeatletter
/// \def\verbatim@nolig@list{\do\`\do\<\do\>\do\'\do\-}
/// \makeatother
/// % ...
/// \begin{Verbatim}[commandchars=\\\{\}]
/// % content goes here
/// \end{Verbatim}
/// ```
///
/// Background color is ignored.
pub fn as_latex_escaped(v: &[(Style, &str)]) -> String {
    let mut s: String = String::new();
    let mut prev_style: Option<Style> = None;
    let mut content: String;
    fn textcolor(style: &Style, first: bool) -> String {
        format!("{}\\textcolor[RGB]{{{},{},{}}}{{",
            if first { "" } else { "}" },
            style.foreground.r,
            style.foreground.b,
            style.foreground.g)
    }
    for &(style, text) in v.iter() {
        if let Some(ps) = prev_style {
            match text {
                " " => {
                    s.push(' ');
                    continue;
                },
                "\n" => continue,
                _ => (),
            }
            if style != ps {
                write!(s, "{}", textcolor(&style, false)).unwrap();
            }
        } else {
            write!(s, "{}", textcolor(&style, true)).unwrap();
        }
        content = text.to_string();
        for &(old, new) in LATEX_REPLACE.iter() {
            content = content.replace(&old, new);
        }
        write!(s, "{}", &content).unwrap();
        prev_style = Some(style);
    }
    s.push('}');
    s
}

/// Print out the various push and pop operations in a vector
/// with visual alignment to the line. Obviously for debugging.
#[cfg(feature = "parsing")]
pub fn debug_print_ops(line: &str, ops: &[(usize, ScopeStackOp)]) {
    for &(i, ref op) in ops.iter() {
        println!("{}", line.trim_end());
        print!("{: <1$}", "", i);
        match *op {
            ScopeStackOp::Push(s) => {
                println!("^ +{}", s);
            }
            ScopeStackOp::Pop(count) => {
                println!("^ pop {}", count);
            }
            ScopeStackOp::Clear(amount) => {
                println!("^ clear {:?}", amount);
            }
            ScopeStackOp::Restore => println!("^ restore"),
            ScopeStackOp::Noop => println!("noop"),
        }
    }
}

/// An iterator over the lines of a string, including the line endings.
///
/// This is similar to the standard library's `lines` method on `str`, except
/// that the yielded lines include the trailing newline character(s).
///
/// You can use it if you're parsing/highlighting some text that you have as a
/// string. With this, you can use the "newlines" variant of syntax definitions,
/// which is recommended.
///
/// # Examples
///
/// ```
/// use syntect::util::LinesWithEndings;
///
/// let mut lines = LinesWithEndings::from("foo\nbar\nbaz");
///
/// assert_eq!(Some("foo\n"), lines.next());
/// assert_eq!(Some("bar\n"), lines.next());
/// assert_eq!(Some("baz"), lines.next());
///
/// assert_eq!(None, lines.next());
/// ```
pub struct LinesWithEndings<'a> {
    input: &'a str,
}

impl<'a> LinesWithEndings<'a> {
    pub fn from(input: &'a str) -> LinesWithEndings<'a> {
        LinesWithEndings { input }
    }
}

impl<'a> Iterator for LinesWithEndings<'a> {
    type Item = &'a str;

    #[inline]
    fn next(&mut self) -> Option<&'a str> {
        if self.input.is_empty() {
            return None;
        }
        let split = self.input
            .find('\n')
            .map(|i| i + 1)
            .unwrap_or_else(|| self.input.len());
        let (line, rest) = self.input.split_at(split);
        self.input = rest;
        Some(line)
    }
}

/// Split a highlighted line at a byte index in the line into a before and
/// after component.
///
/// This is just a helper that does the somewhat tricky logic including splitting
/// a span if the index lies on a boundary.
///
/// This can be used to extract a chunk of the line out for special treatment
/// like wrapping it in an HTML tag for extra styling.
///
/// Generic for testing purposes and fancier use cases, but intended for use with
/// the `Vec<(Style, &str)>` returned by `highlight` methods. Look at the source
/// code for `modify_range` for an example usage.
#[allow(clippy::type_complexity)]
pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'a str)>, Vec<(A, &'a str)>) {
    // This function works by gradually reducing the problem into smaller sub-problems from the front
    let mut rest = v;
    let mut rest_split_i = split_i;

    // Consume all tokens before the split
    let mut before = Vec::new();
    for tok in rest { // Use for instead of a while to avoid bounds checks
        if tok.1.len() > rest_split_i {
            break;
        }
        before.push(tok.clone());
        rest_split_i -= tok.1.len();
    }
    rest = &rest[before.len()..];

    let mut after = Vec::new();
    // If necessary, split the token the split falls inside
    if !rest.is_empty() && rest_split_i > 0 {
        let (sa, sb) = rest[0].1.split_at(rest_split_i);
        before.push((rest[0].0.clone(), sa));
        after.push((rest[0].0.clone(), sb));
        rest = &rest[1..];
    }

    after.extend_from_slice(rest);

    (before, after)
}

/// Modify part of a highlighted line using a style modifier, useful for highlighting sections of a line.
///
/// # Examples
///
/// ```
/// use syntect::util::modify_range;
/// use syntect::highlighting::{Style, StyleModifier, FontStyle};
///
/// let plain = Style::default();
/// let boldmod = StyleModifier { foreground: None, background: None, font_style: Some(FontStyle::BOLD) };
/// let bold = plain.apply(boldmod);
///
/// let l = &[(plain, "abc"), (plain, "def"), (plain, "ghi")];
/// let l2 = modify_range(l, 1..6, boldmod);
/// assert_eq!(l2, &[(plain, "a"), (bold, "bc"), (bold, "def"), (plain, "ghi")]);
/// ```
pub fn modify_range<'a>(v: &[(Style, &'a str)], r: Range<usize>, modifier: StyleModifier) -> Vec<(Style, &'a str)> {
    let (mut result, in_and_after) = split_at(v, r.start);
    let (inside, mut after) = split_at(&in_and_after, r.end - r.start);

    result.extend(inside.iter().map(|(style, s)| { (style.apply(modifier), *s)}));
    result.append(&mut after);
    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_lines_with_endings() {
        fn lines(s: &str) -> Vec<&str> {
            LinesWithEndings::from(s).collect()
        }

        assert!(lines("").is_empty());
        assert_eq!(lines("f"), vec!["f"]);
        assert_eq!(lines("foo"), vec!["foo"]);
        assert_eq!(lines("foo\n"), vec!["foo\n"]);
        assert_eq!(lines("foo\nbar"), vec!["foo\n", "bar"]);
        assert_eq!(lines("foo\nbar\n"), vec!["foo\n", "bar\n"]);
        assert_eq!(lines("foo\r\nbar"), vec!["foo\r\n", "bar"]);
        assert_eq!(lines("foo\r\nbar\r\n"), vec!["foo\r\n", "bar\r\n"]);
        assert_eq!(lines("\nfoo"), vec!["\n", "foo"]);
        assert_eq!(lines("\n\n\n"), vec!["\n", "\n", "\n"]);
    }

    #[test]
    fn test_split_at() {
        let l: &[(u8, &str)] = &[];
        let (before, after) = split_at(l, 0); // empty
        assert_eq!((&before[..], &after[..]), (&[][..],&[][..]));

        let l = &[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")];

        let (before, after) = split_at(l, 0); // at start
        assert_eq!((&before[..], &after[..]), (&[][..],&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 4); // inside token
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "d")][..],&[(1u8, "ef"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 3); // between tokens
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc")][..],&[(1u8, "def"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 9); // just after last token
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));

        let (before, after) = split_at(l, 10); // out of bounds
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));
    }
}
<commit_msg>Blend alpha value on converting colors to ANSI color sequences<commit_after>//! Convenient helper functions for common use cases:
//! * Printing to terminal
//! * Iterating lines with `\n`s
//! * Modifying ranges of highlighted output

use crate::highlighting::{Color, Style, StyleModifier};
#[cfg(feature = "parsing")]
use crate::parsing::ScopeStackOp;
use std::fmt::Write;
use std::ops::Range;

#[inline]
fn blend_fg_color(fg: Color, bg: Color) -> Color {
    if fg.a == 0xff {
        return fg;
    }
    let ratio = fg.a as u32;
    let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
    let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
    let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
    Color {
        r: r as u8,
        g: g as u8,
        b: b as u8,
        a: 255,
    }
}

/// Formats the styled fragments using 24-bit color terminal escape codes.
/// Meant for debugging and testing.
///
/// This function is currently fairly inefficient in its use of escape codes.
///
/// Note that this does not currently ever un-set the color so that the end of a line will also get
/// highlighted with the background.  This means if you might want to use `println!("\x1b[0m");`
/// after to clear the coloring.
///
/// If `bg` is true then the background is also set
pub fn as_24_bit_terminal_escaped(v: &[(Style, &str)], bg: bool) -> String {
    let mut s: String = String::new();
    for &(ref style, text) in v.iter() {
        if bg {
            write!(s,
                   "\x1b[48;2;{};{};{}m",
                   style.background.r,
                   style.background.g,
                   style.background.b)
                .unwrap();
        }
        let fg = blend_fg_color(style.foreground, style.background);
        write!(s, "\x1b[38;2;{};{};{}m{}", fg.r, fg.g, fg.b, text).unwrap();
    }
    // s.push_str("\x1b[0m");
    s
}

const LATEX_REPLACE: [(&str, &str); 3] = [
    ("\\", "\\\\"),
    ("{", "\\{"),
    ("}", "\\}"),
];

/// Formats the styled fragments using LaTeX textcolor directive.
///
/// Usage is similar to the `as_24_bit_terminal_escaped` function:
///
/// ```
/// use syntect::easy::HighlightLines;
/// use syntect::parsing::SyntaxSet;
/// use syntect::highlighting::{ThemeSet,Style};
/// use syntect::util::{as_latex_escaped,LinesWithEndings};
///
/// // Load these once at the start of your program
/// let ps = SyntaxSet::load_defaults_newlines();
/// let ts = ThemeSet::load_defaults();
///
/// let syntax = ps.find_syntax_by_extension("rs").unwrap();
/// let s = "pub struct Wow { hi: u64 }\nfn blah() -> u64 {}\n";
///
/// let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]);
/// for line in LinesWithEndings::from(s) { // LinesWithEndings enables use of newlines mode
///     let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
///     let escaped = as_latex_escaped(&ranges[..]);
///     println!("{}", escaped);
/// }
/// ```
///
/// Returned content is intended to be placed inside a fancyvrb
/// Verbatim environment:
///
/// ```latex
/// \usepackage{fancyvrb}
/// \usepackage{xcolor}
/// % ...
/// % enable comma-separated arguments inside \textcolor
/// \makeatletter
/// \def\verbatim@nolig@list{\do\`\do\<\do\>\do\'\do\-}
/// \makeatother
/// % ...
/// \begin{Verbatim}[commandchars=\\\{\}]
/// % content goes here
/// \end{Verbatim}
/// ```
///
/// Background color is ignored.
pub fn as_latex_escaped(v: &[(Style, &str)]) -> String {
    let mut s: String = String::new();
    let mut prev_style: Option<Style> = None;
    let mut content: String;
    fn textcolor(style: &Style, first: bool) -> String {
        format!("{}\\textcolor[RGB]{{{},{},{}}}{{",
            if first { "" } else { "}" },
            style.foreground.r,
            style.foreground.b,
            style.foreground.g)
    }
    for &(style, text) in v.iter() {
        if let Some(ps) = prev_style {
            match text {
                " " => {
                    s.push(' ');
                    continue;
                },
                "\n" => continue,
                _ => (),
            }
            if style != ps {
                write!(s, "{}", textcolor(&style, false)).unwrap();
            }
        } else {
            write!(s, "{}", textcolor(&style, true)).unwrap();
        }
        content = text.to_string();
        for &(old, new) in LATEX_REPLACE.iter() {
            content = content.replace(&old, new);
        }
        write!(s, "{}", &content).unwrap();
        prev_style = Some(style);
    }
    s.push('}');
    s
}

/// Print out the various push and pop operations in a vector
/// with visual alignment to the line. Obviously for debugging.
#[cfg(feature = "parsing")]
pub fn debug_print_ops(line: &str, ops: &[(usize, ScopeStackOp)]) {
    for &(i, ref op) in ops.iter() {
        println!("{}", line.trim_end());
        print!("{: <1$}", "", i);
        match *op {
            ScopeStackOp::Push(s) => {
                println!("^ +{}", s);
            }
            ScopeStackOp::Pop(count) => {
                println!("^ pop {}", count);
            }
            ScopeStackOp::Clear(amount) => {
                println!("^ clear {:?}", amount);
            }
            ScopeStackOp::Restore => println!("^ restore"),
            ScopeStackOp::Noop => println!("noop"),
        }
    }
}

/// An iterator over the lines of a string, including the line endings.
///
/// This is similar to the standard library's `lines` method on `str`, except
/// that the yielded lines include the trailing newline character(s).
///
/// You can use it if you're parsing/highlighting some text that you have as a
/// string. With this, you can use the "newlines" variant of syntax definitions,
/// which is recommended.
///
/// # Examples
///
/// ```
/// use syntect::util::LinesWithEndings;
///
/// let mut lines = LinesWithEndings::from("foo\nbar\nbaz");
///
/// assert_eq!(Some("foo\n"), lines.next());
/// assert_eq!(Some("bar\n"), lines.next());
/// assert_eq!(Some("baz"), lines.next());
///
/// assert_eq!(None, lines.next());
/// ```
pub struct LinesWithEndings<'a> {
    input: &'a str,
}

impl<'a> LinesWithEndings<'a> {
    pub fn from(input: &'a str) -> LinesWithEndings<'a> {
        LinesWithEndings { input }
    }
}

impl<'a> Iterator for LinesWithEndings<'a> {
    type Item = &'a str;

    #[inline]
    fn next(&mut self) -> Option<&'a str> {
        if self.input.is_empty() {
            return None;
        }
        let split = self.input
            .find('\n')
            .map(|i| i + 1)
            .unwrap_or_else(|| self.input.len());
        let (line, rest) = self.input.split_at(split);
        self.input = rest;
        Some(line)
    }
}

/// Split a highlighted line at a byte index in the line into a before and
/// after component.
///
/// This is just a helper that does the somewhat tricky logic including splitting
/// a span if the index lies on a boundary.
///
/// This can be used to extract a chunk of the line out for special treatment
/// like wrapping it in an HTML tag for extra styling.
///
/// Generic for testing purposes and fancier use cases, but intended for use with
/// the `Vec<(Style, &str)>` returned by `highlight` methods. Look at the source
/// code for `modify_range` for an example usage.
#[allow(clippy::type_complexity)]
pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'a str)>, Vec<(A, &'a str)>) {
    // This function works by gradually reducing the problem into smaller sub-problems from the front
    let mut rest = v;
    let mut rest_split_i = split_i;

    // Consume all tokens before the split
    let mut before = Vec::new();
    for tok in rest { // Use for instead of a while to avoid bounds checks
        if tok.1.len() > rest_split_i {
            break;
        }
        before.push(tok.clone());
        rest_split_i -= tok.1.len();
    }
    rest = &rest[before.len()..];

    let mut after = Vec::new();
    // If necessary, split the token the split falls inside
    if !rest.is_empty() && rest_split_i > 0 {
        let (sa, sb) = rest[0].1.split_at(rest_split_i);
        before.push((rest[0].0.clone(), sa));
        after.push((rest[0].0.clone(), sb));
        rest = &rest[1..];
    }

    after.extend_from_slice(rest);

    (before, after)
}

/// Modify part of a highlighted line using a style modifier, useful for highlighting sections of a line.
///
/// # Examples
///
/// ```
/// use syntect::util::modify_range;
/// use syntect::highlighting::{Style, StyleModifier, FontStyle};
///
/// let plain = Style::default();
/// let boldmod = StyleModifier { foreground: None, background: None, font_style: Some(FontStyle::BOLD) };
/// let bold = plain.apply(boldmod);
///
/// let l = &[(plain, "abc"), (plain, "def"), (plain, "ghi")];
/// let l2 = modify_range(l, 1..6, boldmod);
/// assert_eq!(l2, &[(plain, "a"), (bold, "bc"), (bold, "def"), (plain, "ghi")]);
/// ```
pub fn modify_range<'a>(v: &[(Style, &'a str)], r: Range<usize>, modifier: StyleModifier) -> Vec<(Style, &'a str)> {
    let (mut result, in_and_after) = split_at(v, r.start);
    let (inside, mut after) = split_at(&in_and_after, r.end - r.start);

    result.extend(inside.iter().map(|(style, s)| { (style.apply(modifier), *s)}));
    result.append(&mut after);
    result
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::highlighting::FontStyle;

    #[test]
    fn test_lines_with_endings() {
        fn lines(s: &str) -> Vec<&str> {
            LinesWithEndings::from(s).collect()
        }

        assert!(lines("").is_empty());
        assert_eq!(lines("f"), vec!["f"]);
        assert_eq!(lines("foo"), vec!["foo"]);
        assert_eq!(lines("foo\n"), vec!["foo\n"]);
        assert_eq!(lines("foo\nbar"), vec!["foo\n", "bar"]);
        assert_eq!(lines("foo\nbar\n"), vec!["foo\n", "bar\n"]);
        assert_eq!(lines("foo\r\nbar"), vec!["foo\r\n", "bar"]);
        assert_eq!(lines("foo\r\nbar\r\n"), vec!["foo\r\n", "bar\r\n"]);
        assert_eq!(lines("\nfoo"), vec!["\n", "foo"]);
        assert_eq!(lines("\n\n\n"), vec!["\n", "\n", "\n"]);
    }

    #[test]
    fn test_split_at() {
        let l: &[(u8, &str)] = &[];
        let (before, after) = split_at(l, 0); // empty
        assert_eq!((&before[..], &after[..]), (&[][..],&[][..]));

        let l = &[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")];

        let (before, after) = split_at(l, 0); // at start
        assert_eq!((&before[..], &after[..]), (&[][..],&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 4); // inside token
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "d")][..],&[(1u8, "ef"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 3); // between tokens
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc")][..],&[(1u8, "def"), (2u8, "ghi")][..]));

        let (before, after) = split_at(l, 9); // just after last token
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));

        let (before, after) = split_at(l, 10); // out of bounds
        assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));
    }

    #[test]
    fn test_as_24_bit_terminal_escaped() {
        let style = Style {
            foreground: Color::WHITE,
            background: Color::BLACK,
            font_style: FontStyle::default(),
        };

        // With background
        let s = as_24_bit_terminal_escaped(&[(style, "hello")], true);
        assert_eq!(s, "\x1b[48;2;0;0;0m\x1b[38;2;255;255;255mhello");

        // Without background
        let s = as_24_bit_terminal_escaped(&[(style, "hello")], false);
        assert_eq!(s, "\x1b[38;2;255;255;255mhello");

        // Blend alpha
        let mut foreground = Color::WHITE;
        foreground.a = 128;
        let style = Style {
            foreground,
            background: Color::BLACK,
            font_style: FontStyle::default(),
        };
        let s = as_24_bit_terminal_escaped(&[(style, "hello")], true);
        assert_eq!(s, "\x1b[48;2;0;0;0m\x1b[38;2;128;128;128mhello");
    }
}
<|endoftext|>


<commit_before>extern crate askama;
extern crate proc_macro;
extern crate syn;

use proc_macro::TokenStream;

fn get_path_from_attrs(attrs: &[syn::Attribute]) -> String {
    let attr = attrs.iter().find(|a| a.name() == "template").unwrap();
    if let syn::MetaItem::List(_, ref inner) = attr.value {
        if let syn::NestedMetaItem::MetaItem(ref item) = inner[0] {
            if let &syn::MetaItem::NameValue(ref key, ref val) = item {
                assert_eq!(key.as_ref(), "path");
                if let &syn::Lit::Str(ref s, _) = val {
                    return s.clone();
                }
            }
        }
    }
    panic!("template path not found in struct attributes");
}

#[proc_macro_derive(Template, attributes(template))]
pub fn derive_template(input: TokenStream) -> TokenStream {
    let ast = syn::parse_derive_input(&input.to_string()).unwrap();
    match ast.body {
        syn::Body::Struct(ref data) => data,
        _ => panic!("#[derive(Template)] can only be used with structs"),
    };
    let path = get_path_from_attrs(&ast.attrs);
    askama::build_template(&path, &ast).parse().unwrap()
}
<commit_msg>Define struct to represent template metadata<commit_after>extern crate askama;
extern crate proc_macro;
extern crate syn;

use proc_macro::TokenStream;

struct TemplateMeta {
    path: String,
}

fn get_path_from_attrs(attrs: &[syn::Attribute]) -> TemplateMeta {
    let mut path = None;
    let attr = attrs.iter().find(|a| a.name() == "template").unwrap();
    if let syn::MetaItem::List(_, ref inner) = attr.value {
        for nm_item in inner {
            if let &syn::NestedMetaItem::MetaItem(ref item) = nm_item {
                if let &syn::MetaItem::NameValue(ref key, ref val) = item {
                    match key.as_ref() {
                        "path" => if let &syn::Lit::Str(ref s, _) = val {
                            path = Some(s.clone());
                        } else {
                            panic!("template path must be string literal");
                        },
                        _ => { panic!("unsupported annotation key found") }
                    }
                }
            }
        }
    }
    if path.is_none() {
        panic!("template path not found in struct attributes");
    }
    TemplateMeta { path: path.unwrap() }
}

#[proc_macro_derive(Template, attributes(template))]
pub fn derive_template(input: TokenStream) -> TokenStream {
    let ast = syn::parse_derive_input(&input.to_string()).unwrap();
    match ast.body {
        syn::Body::Struct(ref data) => data,
        _ => panic!("#[derive(Template)] can only be used with structs"),
    };
    let meta = get_path_from_attrs(&ast.attrs);
    askama::build_template(&meta.path, &ast).parse().unwrap()
}
<|endoftext|>


<commit_before>use std::cmp::{max, min};

use glutin::event::ModifiersState;

use alacritty_terminal::grid::BidirectionalIterator;
use alacritty_terminal::index::{Boundary, Point};
use alacritty_terminal::term::search::{Match, RegexSearch};
use alacritty_terminal::term::{Term, TermMode};

use crate::config::ui_config::{Hint, HintAction};
use crate::config::Config;
use crate::display::content::RegexMatches;
use crate::display::MAX_SEARCH_LINES;

/// Percentage of characters in the hints alphabet used for the last character.
const HINT_SPLIT_PERCENTAGE: f32 = 0.5;

/// Keyboard regex hint state.
pub struct HintState {
    /// Hint currently in use.
    hint: Option<Hint>,

    /// Alphabet for hint labels.
    alphabet: String,

    /// Visible matches.
    matches: RegexMatches,

    /// Key label for each visible match.
    labels: Vec<Vec<char>>,

    /// Keys pressed for hint selection.
    keys: Vec<char>,
}

impl HintState {
    /// Initialize an inactive hint state.
    pub fn new<S: Into<String>>(alphabet: S) -> Self {
        Self {
            alphabet: alphabet.into(),
            hint: Default::default(),
            matches: Default::default(),
            labels: Default::default(),
            keys: Default::default(),
        }
    }

    /// Check if a hint selection is in progress.
    pub fn active(&self) -> bool {
        self.hint.is_some()
    }

    /// Start the hint selection process.
    pub fn start(&mut self, hint: Hint) {
        self.hint = Some(hint);
    }

    /// Cancel the hint highlighting process.
    fn stop(&mut self) {
        self.matches.clear();
        self.labels.clear();
        self.keys.clear();
        self.hint = None;
    }

    /// Update the visible hint matches and key labels.
    pub fn update_matches<T>(&mut self, term: &Term<T>) {
        let hint = match self.hint.as_mut() {
            Some(hint) => hint,
            None => return,
        };

        // Find visible matches.
        self.matches.0 = hint.regex.with_compiled(|regex| {
            let mut matches = RegexMatches::new(term, regex);

            // Apply post-processing and search for sub-matches if necessary.
            if hint.post_processing {
                matches
                    .drain(..)
                    .map(|rm| HintPostProcessor::new(term, regex, rm).collect::<Vec<_>>())
                    .flatten()
                    .collect()
            } else {
                matches.0
            }
        });

        // Cancel highlight with no visible matches.
        if self.matches.is_empty() {
            self.stop();
            return;
        }

        let mut generator = HintLabels::new(&self.alphabet, HINT_SPLIT_PERCENTAGE);
        let match_count = self.matches.len();
        let keys_len = self.keys.len();

        // Get the label for each match.
        self.labels.resize(match_count, Vec::new());
        for i in (0..match_count).rev() {
            let mut label = generator.next();
            if label.len() >= keys_len && label[..keys_len] == self.keys[..] {
                self.labels[i] = label.split_off(keys_len);
            } else {
                self.labels[i] = Vec::new();
            }
        }
    }

    /// Handle keyboard input during hint selection.
    pub fn keyboard_input<T>(&mut self, term: &Term<T>, c: char) -> Option<HintMatch> {
        match c {
            // Use backspace to remove the last character pressed.
            '\x08' | '\x1f' => {
                self.keys.pop();
            },
            // Cancel hint highlighting on ESC.
            '\x1b' => self.stop(),
            _ => (),
        }

        // Update the visible matches.
        self.update_matches(term);

        let hint = self.hint.as_ref()?;

        // Find the last label starting with the input character.
        let mut labels = self.labels.iter().enumerate().rev();
        let (index, label) = labels.find(|(_, label)| !label.is_empty() && label[0] == c)?;

        // Check if the selected label is fully matched.
        if label.len() == 1 {
            let bounds = self.matches[index].clone();
            let action = hint.action.clone();

            self.stop();

            Some(HintMatch { action, bounds })
        } else {
            // Store character to preserve the selection.
            self.keys.push(c);

            None
        }
    }

    /// Hint key labels.
    pub fn labels(&self) -> &Vec<Vec<char>> {
        &self.labels
    }

    /// Visible hint regex matches.
    pub fn matches(&self) -> &RegexMatches {
        &self.matches
    }

    /// Update the alphabet used for hint labels.
    pub fn update_alphabet(&mut self, alphabet: &str) {
        if self.alphabet != alphabet {
            self.alphabet = alphabet.to_owned();
            self.keys.clear();
        }
    }
}

/// Hint match which was selected by the user.
#[derive(Debug, Clone)]
pub struct HintMatch {
    /// Action for handling the text.
    pub action: HintAction,

    /// Terminal range matching the hint.
    pub bounds: Match,
}

/// Generator for creating new hint labels.
struct HintLabels {
    /// Full character set available.
    alphabet: Vec<char>,

    /// Alphabet indices for the next label.
    indices: Vec<usize>,

    /// Point separating the alphabet's head and tail characters.
    ///
    /// To make identification of the tail character easy, part of the alphabet cannot be used for
    /// any other position.
    ///
    /// All characters in the alphabet before this index will be used for the last character, while
    /// the rest will be used for everything else.
    split_point: usize,
}

impl HintLabels {
    /// Create a new label generator.
    ///
    /// The `split_ratio` should be a number between 0.0 and 1.0 representing the percentage of
    /// elements in the alphabet which are reserved for the tail of the hint label.
    fn new(alphabet: impl Into<String>, split_ratio: f32) -> Self {
        let alphabet: Vec<char> = alphabet.into().chars().collect();
        let split_point = ((alphabet.len() - 1) as f32 * split_ratio.min(1.)) as usize;

        Self { indices: vec![0], split_point, alphabet }
    }

    /// Get the characters for the next label.
    fn next(&mut self) -> Vec<char> {
        let characters = self.indices.iter().rev().map(|index| self.alphabet[*index]).collect();
        self.increment();
        characters
    }

    /// Increment the character sequence.
    fn increment(&mut self) {
        // Increment the last character; if it's not at the split point we're done.
        let tail = &mut self.indices[0];
        if *tail < self.split_point {
            *tail += 1;
            return;
        }
        *tail = 0;

        // Increment all other characters in reverse order.
        let alphabet_len = self.alphabet.len();
        for index in self.indices.iter_mut().skip(1) {
            if *index + 1 == alphabet_len {
                // Reset character and move to the next if it's already at the limit.
                *index = self.split_point + 1;
            } else {
                // If the character can be incremented, we're done.
                *index += 1;
                return;
            }
        }

        // Extend the sequence with another character when nothing could be incremented.
        self.indices.push(self.split_point + 1);
    }
}

/// Check if there is a hint highlighted at the specified point.
pub fn highlighted_at<T>(
    term: &Term<T>,
    config: &Config,
    point: Point,
    mouse_mods: ModifiersState,
) -> Option<HintMatch> {
    let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE);

    config.ui_config.hints.enabled.iter().find_map(|hint| {
        // Check if all required modifiers are pressed.
        let highlight = hint.mouse.map_or(false, |mouse| {
            mouse.enabled
                && mouse_mods.contains(mouse.mods.0)
                && (!mouse_mode || mouse_mods.contains(ModifiersState::SHIFT))
        });
        if !highlight {
            return None;
        }

        hint.regex.with_compiled(|regex| {
            // Setup search boundaries.
            let mut start = term.line_search_left(point);
            start.line = max(start.line, point.line - MAX_SEARCH_LINES);
            let mut end = term.line_search_right(point);
            end.line = min(end.line, point.line + MAX_SEARCH_LINES);

            // Function to verify if the specified point is inside the match.
            let at_point = |rm: &Match| *rm.start() <= point && *rm.end() >= point;

            // Check if there's any match at the specified point.
            let regex_match = term.regex_search_right(regex, start, end).filter(at_point)?;

            // Apply post-processing and search for sub-matches if necessary.
            let regex_match = if hint.post_processing {
                HintPostProcessor::new(term, regex, regex_match).find(at_point)
            } else {
                Some(regex_match)
            };

            regex_match.map(|bounds| HintMatch { action: hint.action.clone(), bounds })
        })
    })
}

/// Iterator over all post-processed matches inside an existing hint match.
struct HintPostProcessor<'a, T> {
    /// Regex search DFAs.
    regex: &'a RegexSearch,

    /// Terminal reference.
    term: &'a Term<T>,

    /// Next hint match in the iterator.
    next_match: Option<Match>,

    /// Start point for the next search.
    start: Point,

    /// End point for the hint match iterator.
    end: Point,
}

impl<'a, T> HintPostProcessor<'a, T> {
    /// Create a new iterator for an unprocessed match.
    fn new(term: &'a Term<T>, regex: &'a RegexSearch, regex_match: Match) -> Self {
        let end = *regex_match.end();
        let mut post_processor = Self { next_match: None, start: end, end, term, regex };

        // Post-process the first hint match.
        let next_match = post_processor.hint_post_processing(&regex_match);
        post_processor.start = next_match.end().add(term, Boundary::Grid, 1);
        post_processor.next_match = Some(next_match);

        post_processor
    }

    /// Apply some hint post processing heuristics.
    ///
    /// This will check the end of the hint and make it shorter if certain characters are determined
    /// to be unlikely to be intentionally part of the hint.
    ///
    /// This is most useful for identifying URLs appropriately.
    fn hint_post_processing(&self, regex_match: &Match) -> Match {
        let mut iter = self.term.grid().iter_from(*regex_match.start());

        let mut c = iter.cell().c;

        // Truncate uneven number of brackets.
        let end = *regex_match.end();
        let mut open_parents = 0;
        let mut open_brackets = 0;
        loop {
            match c {
                '(' => open_parents += 1,
                '[' => open_brackets += 1,
                ')' => {
                    if open_parents == 0 {
                        iter.prev();
                        break;
                    } else {
                        open_parents -= 1;
                    }
                },
                ']' => {
                    if open_brackets == 0 {
                        iter.prev();
                        break;
                    } else {
                        open_brackets -= 1;
                    }
                },
                _ => (),
            }

            if iter.point() == end {
                break;
            }

            match iter.next() {
                Some(indexed) => c = indexed.cell.c,
                None => break,
            }
        }

        // Truncate trailing characters which are likely to be delimiters.
        let start = *regex_match.start();
        while iter.point() != start {
            if !matches!(c, '.' | ',' | ':' | ';' | '?' | '!' | '(' | '[' | '\'') {
                break;
            }

            match iter.prev() {
                Some(indexed) => c = indexed.cell.c,
                None => break,
            }
        }

        start..=iter.point()
    }
}

impl<'a, T> Iterator for HintPostProcessor<'a, T> {
    type Item = Match;

    fn next(&mut self) -> Option<Self::Item> {
        let next_match = self.next_match.take()?;

        if self.start <= self.end {
            if let Some(rm) = self.term.regex_search_right(self.regex, self.start, self.end) {
                let regex_match = self.hint_post_processing(&rm);
                self.start = regex_match.end().add(self.term, Boundary::Grid, 1);
                self.next_match = Some(regex_match);
            }
        }

        Some(next_match)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hint_label_generation() {
        let mut generator = HintLabels::new("0123", 0.5);

        assert_eq!(generator.next(), vec!['0']);
        assert_eq!(generator.next(), vec!['1']);

        assert_eq!(generator.next(), vec!['2', '0']);
        assert_eq!(generator.next(), vec!['2', '1']);
        assert_eq!(generator.next(), vec!['3', '0']);
        assert_eq!(generator.next(), vec!['3', '1']);

        assert_eq!(generator.next(), vec!['2', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '1']);

        assert_eq!(generator.next(), vec!['2', '2', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '2', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '3', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '3', '1']);
    }
}
<commit_msg>Fix highlighting multiple hints in the same line<commit_after>use std::cmp::{max, min};

use glutin::event::ModifiersState;

use alacritty_terminal::grid::BidirectionalIterator;
use alacritty_terminal::index::{Boundary, Direction, Point};
use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch};
use alacritty_terminal::term::{Term, TermMode};

use crate::config::ui_config::{Hint, HintAction};
use crate::config::Config;
use crate::display::content::RegexMatches;
use crate::display::MAX_SEARCH_LINES;

/// Percentage of characters in the hints alphabet used for the last character.
const HINT_SPLIT_PERCENTAGE: f32 = 0.5;

/// Keyboard regex hint state.
pub struct HintState {
    /// Hint currently in use.
    hint: Option<Hint>,

    /// Alphabet for hint labels.
    alphabet: String,

    /// Visible matches.
    matches: RegexMatches,

    /// Key label for each visible match.
    labels: Vec<Vec<char>>,

    /// Keys pressed for hint selection.
    keys: Vec<char>,
}

impl HintState {
    /// Initialize an inactive hint state.
    pub fn new<S: Into<String>>(alphabet: S) -> Self {
        Self {
            alphabet: alphabet.into(),
            hint: Default::default(),
            matches: Default::default(),
            labels: Default::default(),
            keys: Default::default(),
        }
    }

    /// Check if a hint selection is in progress.
    pub fn active(&self) -> bool {
        self.hint.is_some()
    }

    /// Start the hint selection process.
    pub fn start(&mut self, hint: Hint) {
        self.hint = Some(hint);
    }

    /// Cancel the hint highlighting process.
    fn stop(&mut self) {
        self.matches.clear();
        self.labels.clear();
        self.keys.clear();
        self.hint = None;
    }

    /// Update the visible hint matches and key labels.
    pub fn update_matches<T>(&mut self, term: &Term<T>) {
        let hint = match self.hint.as_mut() {
            Some(hint) => hint,
            None => return,
        };

        // Find visible matches.
        self.matches.0 = hint.regex.with_compiled(|regex| {
            let mut matches = RegexMatches::new(term, regex);

            // Apply post-processing and search for sub-matches if necessary.
            if hint.post_processing {
                matches
                    .drain(..)
                    .map(|rm| HintPostProcessor::new(term, regex, rm).collect::<Vec<_>>())
                    .flatten()
                    .collect()
            } else {
                matches.0
            }
        });

        // Cancel highlight with no visible matches.
        if self.matches.is_empty() {
            self.stop();
            return;
        }

        let mut generator = HintLabels::new(&self.alphabet, HINT_SPLIT_PERCENTAGE);
        let match_count = self.matches.len();
        let keys_len = self.keys.len();

        // Get the label for each match.
        self.labels.resize(match_count, Vec::new());
        for i in (0..match_count).rev() {
            let mut label = generator.next();
            if label.len() >= keys_len && label[..keys_len] == self.keys[..] {
                self.labels[i] = label.split_off(keys_len);
            } else {
                self.labels[i] = Vec::new();
            }
        }
    }

    /// Handle keyboard input during hint selection.
    pub fn keyboard_input<T>(&mut self, term: &Term<T>, c: char) -> Option<HintMatch> {
        match c {
            // Use backspace to remove the last character pressed.
            '\x08' | '\x1f' => {
                self.keys.pop();
            },
            // Cancel hint highlighting on ESC.
            '\x1b' => self.stop(),
            _ => (),
        }

        // Update the visible matches.
        self.update_matches(term);

        let hint = self.hint.as_ref()?;

        // Find the last label starting with the input character.
        let mut labels = self.labels.iter().enumerate().rev();
        let (index, label) = labels.find(|(_, label)| !label.is_empty() && label[0] == c)?;

        // Check if the selected label is fully matched.
        if label.len() == 1 {
            let bounds = self.matches[index].clone();
            let action = hint.action.clone();

            self.stop();

            Some(HintMatch { action, bounds })
        } else {
            // Store character to preserve the selection.
            self.keys.push(c);

            None
        }
    }

    /// Hint key labels.
    pub fn labels(&self) -> &Vec<Vec<char>> {
        &self.labels
    }

    /// Visible hint regex matches.
    pub fn matches(&self) -> &RegexMatches {
        &self.matches
    }

    /// Update the alphabet used for hint labels.
    pub fn update_alphabet(&mut self, alphabet: &str) {
        if self.alphabet != alphabet {
            self.alphabet = alphabet.to_owned();
            self.keys.clear();
        }
    }
}

/// Hint match which was selected by the user.
#[derive(Debug, Clone)]
pub struct HintMatch {
    /// Action for handling the text.
    pub action: HintAction,

    /// Terminal range matching the hint.
    pub bounds: Match,
}

/// Generator for creating new hint labels.
struct HintLabels {
    /// Full character set available.
    alphabet: Vec<char>,

    /// Alphabet indices for the next label.
    indices: Vec<usize>,

    /// Point separating the alphabet's head and tail characters.
    ///
    /// To make identification of the tail character easy, part of the alphabet cannot be used for
    /// any other position.
    ///
    /// All characters in the alphabet before this index will be used for the last character, while
    /// the rest will be used for everything else.
    split_point: usize,
}

impl HintLabels {
    /// Create a new label generator.
    ///
    /// The `split_ratio` should be a number between 0.0 and 1.0 representing the percentage of
    /// elements in the alphabet which are reserved for the tail of the hint label.
    fn new(alphabet: impl Into<String>, split_ratio: f32) -> Self {
        let alphabet: Vec<char> = alphabet.into().chars().collect();
        let split_point = ((alphabet.len() - 1) as f32 * split_ratio.min(1.)) as usize;

        Self { indices: vec![0], split_point, alphabet }
    }

    /// Get the characters for the next label.
    fn next(&mut self) -> Vec<char> {
        let characters = self.indices.iter().rev().map(|index| self.alphabet[*index]).collect();
        self.increment();
        characters
    }

    /// Increment the character sequence.
    fn increment(&mut self) {
        // Increment the last character; if it's not at the split point we're done.
        let tail = &mut self.indices[0];
        if *tail < self.split_point {
            *tail += 1;
            return;
        }
        *tail = 0;

        // Increment all other characters in reverse order.
        let alphabet_len = self.alphabet.len();
        for index in self.indices.iter_mut().skip(1) {
            if *index + 1 == alphabet_len {
                // Reset character and move to the next if it's already at the limit.
                *index = self.split_point + 1;
            } else {
                // If the character can be incremented, we're done.
                *index += 1;
                return;
            }
        }

        // Extend the sequence with another character when nothing could be incremented.
        self.indices.push(self.split_point + 1);
    }
}

/// Check if there is a hint highlighted at the specified point.
pub fn highlighted_at<T>(
    term: &Term<T>,
    config: &Config,
    point: Point,
    mouse_mods: ModifiersState,
) -> Option<HintMatch> {
    let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE);

    config.ui_config.hints.enabled.iter().find_map(|hint| {
        // Check if all required modifiers are pressed.
        let highlight = hint.mouse.map_or(false, |mouse| {
            mouse.enabled
                && mouse_mods.contains(mouse.mods.0)
                && (!mouse_mode || mouse_mods.contains(ModifiersState::SHIFT))
        });
        if !highlight {
            return None;
        }

        hint.regex.with_compiled(|regex| {
            // Setup search boundaries.
            let mut start = term.line_search_left(point);
            start.line = max(start.line, point.line - MAX_SEARCH_LINES);
            let mut end = term.line_search_right(point);
            end.line = min(end.line, point.line + MAX_SEARCH_LINES);

            // Function to verify that the specified point is inside the match.
            let at_point = |rm: &Match| *rm.end() >= point && *rm.start() <= point;

            // Check if there's any match at the specified point.
            let mut iter = RegexIter::new(start, end, Direction::Right, term, regex);
            let regex_match = iter.find(at_point)?;

            // Apply post-processing and search for sub-matches if necessary.
            let regex_match = if hint.post_processing {
                HintPostProcessor::new(term, regex, regex_match).find(at_point)
            } else {
                Some(regex_match)
            };

            regex_match.map(|bounds| HintMatch { action: hint.action.clone(), bounds })
        })
    })
}

/// Iterator over all post-processed matches inside an existing hint match.
struct HintPostProcessor<'a, T> {
    /// Regex search DFAs.
    regex: &'a RegexSearch,

    /// Terminal reference.
    term: &'a Term<T>,

    /// Next hint match in the iterator.
    next_match: Option<Match>,

    /// Start point for the next search.
    start: Point,

    /// End point for the hint match iterator.
    end: Point,
}

impl<'a, T> HintPostProcessor<'a, T> {
    /// Create a new iterator for an unprocessed match.
    fn new(term: &'a Term<T>, regex: &'a RegexSearch, regex_match: Match) -> Self {
        let end = *regex_match.end();
        let mut post_processor = Self { next_match: None, start: end, end, term, regex };

        // Post-process the first hint match.
        let next_match = post_processor.hint_post_processing(&regex_match);
        post_processor.start = next_match.end().add(term, Boundary::Grid, 1);
        post_processor.next_match = Some(next_match);

        post_processor
    }

    /// Apply some hint post processing heuristics.
    ///
    /// This will check the end of the hint and make it shorter if certain characters are determined
    /// to be unlikely to be intentionally part of the hint.
    ///
    /// This is most useful for identifying URLs appropriately.
    fn hint_post_processing(&self, regex_match: &Match) -> Match {
        let mut iter = self.term.grid().iter_from(*regex_match.start());

        let mut c = iter.cell().c;

        // Truncate uneven number of brackets.
        let end = *regex_match.end();
        let mut open_parents = 0;
        let mut open_brackets = 0;
        loop {
            match c {
                '(' => open_parents += 1,
                '[' => open_brackets += 1,
                ')' => {
                    if open_parents == 0 {
                        iter.prev();
                        break;
                    } else {
                        open_parents -= 1;
                    }
                },
                ']' => {
                    if open_brackets == 0 {
                        iter.prev();
                        break;
                    } else {
                        open_brackets -= 1;
                    }
                },
                _ => (),
            }

            if iter.point() == end {
                break;
            }

            match iter.next() {
                Some(indexed) => c = indexed.cell.c,
                None => break,
            }
        }

        // Truncate trailing characters which are likely to be delimiters.
        let start = *regex_match.start();
        while iter.point() != start {
            if !matches!(c, '.' | ',' | ':' | ';' | '?' | '!' | '(' | '[' | '\'') {
                break;
            }

            match iter.prev() {
                Some(indexed) => c = indexed.cell.c,
                None => break,
            }
        }

        start..=iter.point()
    }
}

impl<'a, T> Iterator for HintPostProcessor<'a, T> {
    type Item = Match;

    fn next(&mut self) -> Option<Self::Item> {
        let next_match = self.next_match.take()?;

        if self.start <= self.end {
            if let Some(rm) = self.term.regex_search_right(self.regex, self.start, self.end) {
                let regex_match = self.hint_post_processing(&rm);
                self.start = regex_match.end().add(self.term, Boundary::Grid, 1);
                self.next_match = Some(regex_match);
            }
        }

        Some(next_match)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hint_label_generation() {
        let mut generator = HintLabels::new("0123", 0.5);

        assert_eq!(generator.next(), vec!['0']);
        assert_eq!(generator.next(), vec!['1']);

        assert_eq!(generator.next(), vec!['2', '0']);
        assert_eq!(generator.next(), vec!['2', '1']);
        assert_eq!(generator.next(), vec!['3', '0']);
        assert_eq!(generator.next(), vec!['3', '1']);

        assert_eq!(generator.next(), vec!['2', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '1']);

        assert_eq!(generator.next(), vec!['2', '2', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '2', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '2', '3', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '2', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '2', '1']);
        assert_eq!(generator.next(), vec!['2', '3', '3', '0']);
        assert_eq!(generator.next(), vec!['2', '3', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '2', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '2', '3', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '2', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '2', '1']);
        assert_eq!(generator.next(), vec!['3', '3', '3', '0']);
        assert_eq!(generator.next(), vec!['3', '3', '3', '1']);
    }
}
<|endoftext|>


<commit_before>extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate stdweb;

extern crate reproto_backend_java as java;
extern crate reproto_backend_js as js;
extern crate reproto_backend_json as json;
extern crate reproto_backend_python as python;
extern crate reproto_backend_reproto as reproto;
extern crate reproto_backend_rust as rust;
extern crate reproto_compile as compile;
extern crate reproto_core as core;
extern crate reproto_derive as derive;
extern crate reproto_manifest as manifest;
extern crate reproto_parser as parser;

use std::cell::RefCell;
use std::rc::Rc;
use std::str;
use std::sync::Arc;

#[derive(Debug, Clone, Serialize, Deserialize)]
enum Format {
    #[serde(rename = "json")]
    Json,
    #[serde(rename = "yaml")]
    Yaml,
    #[serde(rename = "reproto")]
    Reproto,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
enum Output {
    #[serde(rename = "reproto")]
    Reproto,
    #[serde(rename = "java")]
    Java,
    #[serde(rename = "python")]
    Python,
    #[serde(rename = "rust")]
    Rust,
    #[serde(rename = "js")]
    JavaScript,
    #[serde(rename = "json")]
    Json,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct JavaSettings {
    jackson: bool,
    lombok: bool,
}

js_serializable!(JavaSettings);
js_deserializable!(JavaSettings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct RustSettings {
    chrono: bool,
}

js_serializable!(RustSettings);
js_deserializable!(RustSettings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Settings {
    java: JavaSettings,
    rust: RustSettings,
}

js_serializable!(Settings);
js_deserializable!(Settings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct File {
    package: String,
    version: Option<String>,
    content: String,
}

js_serializable!(File);
js_deserializable!(File);

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Content {
    #[serde(rename = "content")]
    Content { content: String },
    #[serde(rename = "file_index")]
    FileIndex { index: usize },
}

js_serializable!(Content);
js_deserializable!(Content);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Derive {
    content: Content,
    files: Vec<File>,
    root_name: String,
    format: Format,
    output: Output,
    package_prefix: Option<String>,
    settings: Settings,
}

js_serializable!(Derive);
js_deserializable!(Derive);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Marker {
    message: String,
    row_start: u32,
    row_end: u32,
    col_start: u32,
    col_end: u32,
}

impl Marker {
    /// Convert an error into a marker.
    fn try_from_error(p: &core::ErrorPos, message: &str) -> core::errors::Result<Marker> {
        let (_, line, (s, e)) = core::utils::find_line(p.object.read()?, (p.start, p.end))?;

        let marker = Marker {
            message: message.to_string(),
            row_start: line as u32,
            row_end: line as u32,
            col_start: s as u32,
            col_end: e as u32,
        };

        Ok(marker)
    }
}

js_serializable!(Marker);
js_deserializable!(Marker);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct DeriveResult {
    result: Option<String>,
    error: Option<String>,
    error_markers: Vec<Marker>,
    info_markers: Vec<Marker>,
}

js_serializable!(DeriveResult);
js_deserializable!(DeriveResult);

#[derive(Debug, Clone)]
struct ParsedFile {
    package: core::RpPackage,
    version: Option<core::Version>,
    content: String,
}

/// Resolver using provided files.
struct MapResolver(Vec<ParsedFile>);

impl core::Resolver for MapResolver {
    fn resolve(
        &mut self,
        required: &core::RpRequiredPackage,
    ) -> core::errors::Result<Vec<core::Resolved>> {
        let mut out = Vec::new();

        let package = &required.package;

        for file in self.0.iter() {
            if file.package != required.package {
                continue;
            }

            if file.version
                .as_ref()
                .map(|v| required.range.matches(v))
                .unwrap_or_else(|| required.range.matches_any())
            {
                let bytes = file.content.as_bytes().to_vec();
                let object = Box::new(core::BytesObject::new(package.to_string(), Arc::new(bytes)));

                out.push(core::Resolved {
                    version: file.version.clone(),
                    object: object,
                })
            }
        }

        Ok(out)
    }

    fn resolve_by_prefix(
        &mut self,
        prefix: &core::RpPackage,
    ) -> core::errors::Result<Vec<core::ResolvedByPrefix>> {
        let mut out = Vec::new();

        for file in self.0.iter() {
            if file.package.starts_with(prefix) {
                let bytes = file.content.as_bytes().to_vec();

                let object = Box::new(core::BytesObject::new(
                    file.package.to_string(),
                    Arc::new(bytes),
                ));

                out.push(core::ResolvedByPrefix {
                    package: file.package.clone(),
                    object: object,
                })
            }
        }

        Ok(out)
    }
}

fn derive(derive: Derive) -> DeriveResult {
    let errors = Rc::new(RefCell::new(Vec::new()));

    return match try_derive(derive, errors.clone()) {
        Ok(result) => DeriveResult {
            result: Some(result),
            error: None,
            error_markers: vec![],
            info_markers: vec![],
        },
        Err(e) => {
            let mut error_markers = Vec::new();
            let mut info_markers = Vec::new();

            if let Some(p) = e.pos() {
                error_markers
                    .push(Marker::try_from_error(p, e.message()).expect("Failed to build marker"));
            }

            for e in errors.borrow().iter() {
                match *e {
                    core::ContextItem::ErrorPos(ref p, ref message) => {
                        error_markers.push(
                            Marker::try_from_error(p, message.as_str())
                                .expect("Failed to build marker"),
                        );
                    }
                    core::ContextItem::InfoPos(ref p, ref message) => {
                        info_markers.push(
                            Marker::try_from_error(p, message.as_str())
                                .expect("Failed to build marker"),
                        );
                    }
                }
            }

            DeriveResult {
                result: None,
                error: Some(e.display().to_string()),
                error_markers: error_markers,
                info_markers: info_markers,
            }
        }
    };

    fn try_derive(
        derive: Derive,
        errors: Rc<RefCell<Vec<core::ContextItem>>>,
    ) -> core::errors::Result<String> {
        let (object, package) = match derive.content {
            Content::Content { ref content } => {
                let bytes = content.as_bytes().to_vec();
                let object = core::BytesObject::new("web".to_string(), Arc::new(bytes));

                (object, None)
            }
            Content::FileIndex { index } => {
                let file = derive
                    .files
                    .get(index)
                    .ok_or_else(|| format!("No file for index: {}", index))?;

                let bytes = file.content.as_bytes().to_vec();
                let object = core::BytesObject::new(file.package.to_string(), Arc::new(bytes));

                let package = parse_package(&file)?;

                (object, Some(package))
            }
        };

        let package_prefix = derive
            .package_prefix
            .as_ref()
            .map(|s| core::RpPackage::parse(s))
            .unwrap_or_else(|| core::RpPackage::parse("io.reproto.github"));

        let input = match derive.format {
            Format::Json => derive_file(&derive, &package_prefix, &object, Box::new(derive::Json))?,
            Format::Yaml => derive_file(&derive, &package_prefix, &object, Box::new(derive::Yaml))?,
            Format::Reproto => compile::Input::Object(Box::new(object), package),
        };

        let mut buf = String::new();

        let files = parse_files(derive.files)?;

        let resolver = Box::new(MapResolver(files));

        let simple_compile = compile::SimpleCompile::new(input)
            .with_errors(errors)
            .resolver(resolver)
            .package_prefix(package_prefix);

        match derive.output {
            Output::Reproto => {
                let mut modules = Vec::new();

                compile::simple_compile::<reproto::ReprotoLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    reproto::compile,
                )?;
            }
            Output::Java => {
                let mut modules = Vec::new();

                if derive.settings.java.jackson {
                    modules.push(java::JavaModule::Jackson);
                }

                if derive.settings.java.lombok {
                    modules.push(java::JavaModule::Lombok);
                }

                compile::simple_compile::<java::JavaLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    java::compile,
                )?;
            }
            Output::Python => {
                let mut modules = Vec::new();

                compile::simple_compile::<python::PythonLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    python::compile,
                )?;
            }
            Output::Rust => {
                let mut modules = Vec::new();

                if derive.settings.rust.chrono {
                    modules.push(rust::RustModule::Chrono);
                }

                compile::simple_compile::<rust::RustLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    rust::compile,
                )?;
            }
            Output::JavaScript => {
                let mut modules = Vec::new();

                compile::simple_compile::<js::JsLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    js::compile,
                )?;
            }
            Output::Json => {
                let mut modules = Vec::new();

                compile::simple_compile::<json::JsonLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    json::compile,
                )?;
            }
        }

        Ok(buf)
    }

    fn parse_files(files: Vec<File>) -> core::errors::Result<Vec<ParsedFile>> {
        let mut out: Vec<ParsedFile> = Vec::new();

        for f in files {
            let package = parse_package(&f)?;

            out.push(ParsedFile {
                package: package.package,
                version: package.version,
                content: f.content,
            });
        }

        Ok(out)
    }

    fn parse_package(file: &File) -> core::errors::Result<core::RpVersionedPackage> {
        let package = core::RpPackage::parse(file.package.as_str());

        let version = if let Some(ref version) = file.version {
            Some(core::Version::parse(version.as_str())
                .map_err(|e| format!("{}: failed to parse version `{}`: {}", package, version, e))?)
        } else {
            None
        };

        Ok(core::RpVersionedPackage::new(package, version))
    }

    fn derive_file(
        derive: &Derive,
        package_prefix: &core::RpPackage,
        object: &core::Object,
        format: Box<derive::Format>,
    ) -> core::errors::Result<compile::Input> {
        let decl = derive::derive(
            derive::Derive::new(
                derive.root_name.to_string(),
                format,
                Some(package_prefix.clone()),
            ),
            object,
        )?;

        let file = core::RpFile {
            comment: vec!["Generated from reproto derive".to_string()],
            options: vec![],
            decls: vec![decl],
        };

        let input = compile::Input::RpFile(
            file,
            Some(core::RpVersionedPackage::new(package_prefix.clone(), None)),
        );

        Ok(input)
    }
}

fn main() {
    stdweb::initialize();

    js! {
        Module.exports.derive = @{derive};
    }
}
<commit_msg>Non-panicky marker construction in eval<commit_after>extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate stdweb;

extern crate reproto_backend_java as java;
extern crate reproto_backend_js as js;
extern crate reproto_backend_json as json;
extern crate reproto_backend_python as python;
extern crate reproto_backend_reproto as reproto;
extern crate reproto_backend_rust as rust;
extern crate reproto_compile as compile;
extern crate reproto_core as core;
extern crate reproto_derive as derive;
extern crate reproto_manifest as manifest;
extern crate reproto_parser as parser;

use std::cell::RefCell;
use std::rc::Rc;
use std::str;
use std::sync::Arc;

#[derive(Debug, Clone, Serialize, Deserialize)]
enum Format {
    #[serde(rename = "json")]
    Json,
    #[serde(rename = "yaml")]
    Yaml,
    #[serde(rename = "reproto")]
    Reproto,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
enum Output {
    #[serde(rename = "reproto")]
    Reproto,
    #[serde(rename = "java")]
    Java,
    #[serde(rename = "python")]
    Python,
    #[serde(rename = "rust")]
    Rust,
    #[serde(rename = "js")]
    JavaScript,
    #[serde(rename = "json")]
    Json,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct JavaSettings {
    jackson: bool,
    lombok: bool,
}

js_serializable!(JavaSettings);
js_deserializable!(JavaSettings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct RustSettings {
    chrono: bool,
}

js_serializable!(RustSettings);
js_deserializable!(RustSettings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Settings {
    java: JavaSettings,
    rust: RustSettings,
}

js_serializable!(Settings);
js_deserializable!(Settings);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct File {
    package: String,
    version: Option<String>,
    content: String,
}

js_serializable!(File);
js_deserializable!(File);

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Content {
    #[serde(rename = "content")]
    Content { content: String },
    #[serde(rename = "file_index")]
    FileIndex { index: usize },
}

js_serializable!(Content);
js_deserializable!(Content);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Derive {
    content: Content,
    files: Vec<File>,
    root_name: String,
    format: Format,
    output: Output,
    package_prefix: Option<String>,
    settings: Settings,
}

js_serializable!(Derive);
js_deserializable!(Derive);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Marker {
    message: String,
    row_start: u32,
    row_end: u32,
    col_start: u32,
    col_end: u32,
}

impl Marker {
    /// Convert an error into a marker.
    fn try_from_error(p: &core::ErrorPos, message: &str) -> core::errors::Result<Marker> {
        let (_, line, (s, e)) = core::utils::find_line(p.object.read()?, (p.start, p.end))?;

        let marker = Marker {
            message: message.to_string(),
            row_start: line as u32,
            row_end: line as u32,
            col_start: s as u32,
            col_end: e as u32,
        };

        Ok(marker)
    }

    /// Safe building of markers with fallback.
    fn try_from_error_fb(p: &core::ErrorPos, message: &str) -> Marker {
        if let Ok(m) = Self::try_from_error(p, message) {
            return m;
        }

        // no positional information
        Self::safe_from_error(message)
    }

    /// Safely build a marker.
    fn safe_from_error(message: &str) -> Marker {
        Marker {
            message: message.to_string(),
            row_start: 0,
            row_end: 0,
            col_start: 0,
            col_end: 0,
        }
    }
}

js_serializable!(Marker);
js_deserializable!(Marker);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct DeriveResult {
    result: Option<String>,
    error: Option<String>,
    error_markers: Vec<Marker>,
    info_markers: Vec<Marker>,
}

js_serializable!(DeriveResult);
js_deserializable!(DeriveResult);

#[derive(Debug, Clone)]
struct ParsedFile {
    package: core::RpPackage,
    version: Option<core::Version>,
    content: String,
}

/// Resolver using provided files.
struct MapResolver(Vec<ParsedFile>);

impl core::Resolver for MapResolver {
    fn resolve(
        &mut self,
        required: &core::RpRequiredPackage,
    ) -> core::errors::Result<Vec<core::Resolved>> {
        let mut out = Vec::new();

        let package = &required.package;

        for file in self.0.iter() {
            if file.package != required.package {
                continue;
            }

            if file.version
                .as_ref()
                .map(|v| required.range.matches(v))
                .unwrap_or_else(|| required.range.matches_any())
            {
                let bytes = file.content.as_bytes().to_vec();
                let object = Box::new(core::BytesObject::new(package.to_string(), Arc::new(bytes)));

                out.push(core::Resolved {
                    version: file.version.clone(),
                    object: object,
                })
            }
        }

        Ok(out)
    }

    fn resolve_by_prefix(
        &mut self,
        prefix: &core::RpPackage,
    ) -> core::errors::Result<Vec<core::ResolvedByPrefix>> {
        let mut out = Vec::new();

        for file in self.0.iter() {
            if file.package.starts_with(prefix) {
                let bytes = file.content.as_bytes().to_vec();

                let object = Box::new(core::BytesObject::new(
                    file.package.to_string(),
                    Arc::new(bytes),
                ));

                out.push(core::ResolvedByPrefix {
                    package: file.package.clone(),
                    object: object,
                })
            }
        }

        Ok(out)
    }
}

fn derive(derive: Derive) -> DeriveResult {
    let errors = Rc::new(RefCell::new(Vec::new()));

    return match try_derive(derive, errors.clone()) {
        Ok(result) => DeriveResult {
            result: Some(result),
            error: None,
            error_markers: vec![],
            info_markers: vec![],
        },
        Err(e) => {
            let mut error_markers = Vec::new();
            let mut info_markers = Vec::new();

            if let Some(p) = e.pos() {
                error_markers.push(Marker::try_from_error_fb(p, e.message()));
            }

            for e in errors.borrow().iter() {
                match *e {
                    core::ContextItem::ErrorPos(ref p, ref message) => {
                        error_markers.push(Marker::try_from_error_fb(p, message.as_str()));
                    }
                    core::ContextItem::InfoPos(ref p, ref message) => {
                        info_markers.push(Marker::try_from_error_fb(p, message.as_str()));
                    }
                }
            }

            DeriveResult {
                result: None,
                error: Some(e.display().to_string()),
                error_markers: error_markers,
                info_markers: info_markers,
            }
        }
    };

    fn try_derive(
        derive: Derive,
        errors: Rc<RefCell<Vec<core::ContextItem>>>,
    ) -> core::errors::Result<String> {
        let (object, package) = match derive.content {
            Content::Content { ref content } => {
                let bytes = content.as_bytes().to_vec();
                let object = core::BytesObject::new("web".to_string(), Arc::new(bytes));

                (object, None)
            }
            Content::FileIndex { index } => {
                let file = derive
                    .files
                    .get(index)
                    .ok_or_else(|| format!("No file for index: {}", index))?;

                let bytes = file.content.as_bytes().to_vec();
                let object = core::BytesObject::new(file.package.to_string(), Arc::new(bytes));

                let package = parse_package(&file)?;

                (object, Some(package))
            }
        };

        let package_prefix = derive
            .package_prefix
            .as_ref()
            .map(|s| core::RpPackage::parse(s))
            .unwrap_or_else(|| core::RpPackage::parse("io.reproto.github"));

        let input = match derive.format {
            Format::Json => derive_file(&derive, &package_prefix, &object, Box::new(derive::Json))?,
            Format::Yaml => derive_file(&derive, &package_prefix, &object, Box::new(derive::Yaml))?,
            Format::Reproto => compile::Input::Object(Box::new(object), package),
        };

        let mut buf = String::new();

        let files = parse_files(derive.files)?;

        let resolver = Box::new(MapResolver(files));

        let simple_compile = compile::SimpleCompile::new(input)
            .with_errors(errors)
            .resolver(resolver)
            .package_prefix(package_prefix);

        match derive.output {
            Output::Reproto => {
                let mut modules = Vec::new();

                compile::simple_compile::<reproto::ReprotoLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    reproto::compile,
                )?;
            }
            Output::Java => {
                let mut modules = Vec::new();

                if derive.settings.java.jackson {
                    modules.push(java::JavaModule::Jackson);
                }

                if derive.settings.java.lombok {
                    modules.push(java::JavaModule::Lombok);
                }

                compile::simple_compile::<java::JavaLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    java::compile,
                )?;
            }
            Output::Python => {
                let mut modules = Vec::new();

                compile::simple_compile::<python::PythonLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    python::compile,
                )?;
            }
            Output::Rust => {
                let mut modules = Vec::new();

                if derive.settings.rust.chrono {
                    modules.push(rust::RustModule::Chrono);
                }

                compile::simple_compile::<rust::RustLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    rust::compile,
                )?;
            }
            Output::JavaScript => {
                let mut modules = Vec::new();

                compile::simple_compile::<js::JsLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    js::compile,
                )?;
            }
            Output::Json => {
                let mut modules = Vec::new();

                compile::simple_compile::<json::JsonLang, _>(
                    &mut buf,
                    simple_compile,
                    modules,
                    json::compile,
                )?;
            }
        }

        Ok(buf)
    }

    fn parse_files(files: Vec<File>) -> core::errors::Result<Vec<ParsedFile>> {
        let mut out: Vec<ParsedFile> = Vec::new();

        for f in files {
            let package = parse_package(&f)?;

            out.push(ParsedFile {
                package: package.package,
                version: package.version,
                content: f.content,
            });
        }

        Ok(out)
    }

    fn parse_package(file: &File) -> core::errors::Result<core::RpVersionedPackage> {
        let package = core::RpPackage::parse(file.package.as_str());

        let version = if let Some(ref version) = file.version {
            Some(core::Version::parse(version.as_str())
                .map_err(|e| format!("{}: failed to parse version `{}`: {}", package, version, e))?)
        } else {
            None
        };

        Ok(core::RpVersionedPackage::new(package, version))
    }

    fn derive_file(
        derive: &Derive,
        package_prefix: &core::RpPackage,
        object: &core::Object,
        format: Box<derive::Format>,
    ) -> core::errors::Result<compile::Input> {
        let decl = derive::derive(
            derive::Derive::new(
                derive.root_name.to_string(),
                format,
                Some(package_prefix.clone()),
            ),
            object,
        )?;

        let file = core::RpFile {
            comment: vec!["Generated from reproto derive".to_string()],
            options: vec![],
            decls: vec![decl],
        };

        let input = compile::Input::RpFile(
            file,
            Some(core::RpVersionedPackage::new(package_prefix.clone(), None)),
        );

        Ok(input)
    }
}

fn main() {
    stdweb::initialize();

    js! {
        Module.exports.derive = @{derive};
    }
}
<|endoftext|>