cursive
cursive copied to clipboard
Ask for help: How to highlight words?
Question
I want add to my text redactor rust code syntax highlight, how i can do this?
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:
- We start from a source string.
- We define "spans" of this string on which to apply different styles. For instance, the first 8 characters should be plain text, then 4 characters of red text, and so on. This is currently done by markup parsers, but could also be done by syntax highlighter parsers.
- We store both the source string and the attached span descriptors as a
StyledString: it defines a text and how it should be "styled". - The
TextView(or other view) prints itsStyledStringaccorging to instructions.
So if you want to have an editor with syntax highlighting, you will need:
- A syntax parser that identifies what style to give to each piece of text. It may look like the current markdown parser, only for a different syntax.
- An updated
TextAreathat may "style" its content. I haven't though about it much yet, but it may end up in the officialTextArea.
Regarding the parser itself, syntext may be an option.
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.
Note that since then, cursive-syntect was added to simplify using syntect to generate a StyledString.