gyscos / cursive

A Text User Interface library for the Rust programming language
MIT License
4.3k stars 245 forks source link

Ask for help: How to highlight words? #189

Open ghost opened 6 years ago

ghost commented 6 years ago

Question

I want add to my text redactor rust code syntax highlight, how i can do this?

gyscos commented 6 years ago

The "highlight" term in cursive is used to describe selected items like buttons, that appear with inverted colors (background and foreground colors swap).

"Highlight" as in "syntax highlighting" is about giving different colors or effects to parts of the text. This is only recently supported (not even published yet to crates.io), and is currently only supported for TextView that display fixed content, not for an editable TextArea.

Here is how this works currently:

So if you want to have an editor with syntax highlighting, you will need:

gyscos commented 6 years ago

Regarding the parser itself, syntext may be an option.

aimerib commented 2 years ago

I know this is a really old issue, but for those who find this issue in the future as I did, the way I solved this was somewhat similar to what @gyscos suggested, so I'm leaving it here in case it might inspire others. This is pretty hacky, so any suggestions are appreciated:

    let mut new_message = message.clone();
    if templated_words.len() > 0 {
        for word in templated_words.iter() {
            let re_matcher = format!(r#"({})"#, word.clone());
            let re = Regex::new(re_matcher.as_str()).unwrap();
            for capture in re.captures_iter(new_message.clone().as_str()) {
                 // I'm implementing a simple templating for highlighting words but matching
                 // highlighted words separated by spaces was something I didn't know how to do
                 // so I replace the spaces with underscores first, so I'm only looking for "one" word
                let plain_word = capture.get(1).unwrap().as_str().replace(" ", "_");
                let start = capture.get(1).unwrap().start();
                let end = capture.get(1).unwrap().end();
                new_message = new_message.clone()[..start].to_string()
                    + &plain_word[..]
                    + &new_message.clone()[end..];
            }
        }
        let styled_room_text = new_message
            .lines()
            .map(|sentence| {
                let styled_sentence = sentence
                    .split(' ')
                    .map(|word| {
                        // now I can split that templated word above and join with a space again, so I'm working with
                        // one string that might have spaces in it
                        let trimmed_word = &word.split("_").collect::<Vec<&str>>().join(" ");
                        let sanitized_word: String;

                        // this part here is so that I could still highlight a word if it was next to a punctuation mark.
                        // this is a very brittle and naive implementation that works for my project but I can't recommend it.
                        let last_char = if trimmed_word.len() > 0 {
                            trimmed_word.clone().chars().last().unwrap()
                        } else {
                            ' '
                        };
                        if last_char == '.'
                            || last_char == '!'
                            || last_char == '?'
                            || last_char == ','
                        {
                            sanitized_word = trimmed_word[..trimmed_word.len() - 1].to_string();
                        } else {
                            sanitized_word = trimmed_word.to_string();
                        }
                        // templated_words is a Vec<String> that might contain strings with spaces in them so that's why the work
                        // above with splitting and replacing is needed
                        if templated_words.contains(&sanitized_word) {
                            let mut styled_word = SpannedString::styled(
                                sanitized_word,
                                Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
                            );
                            // we highlight the word above, and then append the corresponding punctuation as a plain string
                            if last_char == '.' {
                                styled_word.append(".");
                            }
                            if last_char == '!' {
                                styled_word.append("!");
                            }
                            if last_char == '?' {
                                styled_word.append("?");
                            }
                            if last_char == ',' {
                                styled_word.append(",");
                            }
                            styled_word.append_plain(" ");
                            styled_word
                        } else {
                            let mut plain_word = SpannedString::styled(
                                word.to_string(),
                                Style::from(Color::Dark(BaseColor::Green)),
                            );
                            plain_word.append_plain(" ");
                            plain_word
                        }
                    })
                    .reduce(|mut acc, word| {
                        acc.append(word);
                        acc
                    })
                    .unwrap();
                styled_sentence
            })
            .fold(StyledString::new(), |mut s, l| {
                s.append(l);
                s.append_plain("\n");
                s
            });
        styled_room_text
    } else {
        SpannedString::styled(message, Style::from(Color::Dark(BaseColor::Green)))
    }

I hope this can help others too.

gyscos commented 1 year ago

Note that since then, cursive-syntect was added to simplify using syntect to generate a StyledString.