accounts: HashMap<String, Account>,
statuses: HashMap<String, Status>,
notifications: HashMap<String, Notification>,
+ polls: HashMap<String, Poll>,
feeds: HashMap<FeedId, Feed>,
instance: Option<Instance>,
permit_write: bool,
impl ReqParam for i32 {
fn param_value(self) -> String { self.to_string() }
}
+impl ReqParam for usize {
+ fn param_value(self) -> String { self.to_string() }
+}
impl ReqParam for bool {
fn param_value(self) -> String {
match self {
accounts: HashMap::new(),
statuses: HashMap::new(),
notifications: HashMap::new(),
+ polls: HashMap::new(),
feeds: HashMap::new(),
instance: None,
permit_write: false,
fn cache_status(&mut self, st: &Status) {
self.cache_account(&st.account);
+ if let Some(poll) = &st.poll {
+ self.cache_poll(poll);
+ }
self.statuses.insert(st.id.to_string(), st.clone());
if let Some(ref sub) = st.reblog {
self.statuses.insert(sub.id.to_string(), *sub.clone());
self.notifications.insert(n.id.to_string(), n.clone());
}
+ fn cache_poll(&mut self, poll: &Poll) {
+ self.polls.insert(poll.id.to_string(), poll.clone());
+ }
+
pub fn account_by_id(&mut self, id: &str) -> Result<Account, ClientError> {
if let Some(st) = self.accounts.get(id) {
return Ok(st.clone());
Ok(ac)
}
+ pub fn poll_by_id(&mut self, id: &str) -> Result<Poll, ClientError> {
+ if let Some(st) = self.polls.get(id) {
+ return Ok(st.clone());
+ }
+
+ let (url, rsp) = self.api_request(Req::get(
+ &("v1/polls/".to_owned() + id)))?;
+ let rspstatus = rsp.status();
+ let poll: Poll = if !rspstatus.is_success() {
+ Err(ClientError::UrlError(url.clone(), rspstatus.to_string()))
+ } else {
+ match serde_json::from_str(&rsp.text()?) {
+ Ok(poll) => Ok(poll),
+ Err(e) => Err(ClientError::UrlError(
+ url.clone(), e.to_string())),
+ }
+ }?;
+ if poll.id != id {
+ return Err(ClientError::UrlError(
+ url, format!("request returned wrong poll id {}", &poll.id)));
+ }
+ self.cache_poll(&poll);
+ Ok(poll)
+ }
+
pub fn account_relationship_by_id(&mut self, id: &str) ->
Result<Relationship, ClientError>
{
// we had cached
st.account = ac.clone();
}
+ if let Some(poll) = st.poll.as_ref().and_then(
+ |poll| self.polls.get(&poll.id)) {
+ // Ditto with the poll, if any
+ st.poll = Some(poll.clone());
+ }
return Ok(st);
}
}
Ok(ctx)
}
+
+ pub fn vote_in_poll(&mut self, id: &str,
+ choices: impl Iterator<Item = usize>)
+ -> Result<(), ClientError>
+ {
+ let choices: Vec<_> = choices.collect();
+ let mut req = Req::post(&format!("v1/polls/{id}/votes"));
+ for choice in choices {
+ req = req.param("choices[]", choice);
+ }
+ let (url, rsp) = self.api_request(req)?;
+ let rspstatus = rsp.status();
+ // Cache the returned poll so as to update its faved/boosted flags
+ let poll: Poll = if !rspstatus.is_success() {
+ Err(ClientError::UrlError(url, rspstatus.to_string()))
+ } else {
+ match serde_json::from_str(&rsp.text()?) {
+ Ok(poll) => Ok(poll),
+ Err(e) => {
+ Err(ClientError::UrlError(url, e.to_string()))
+ }
+ }
+ }?;
+ self.cache_poll(&poll);
+ Ok(())
+ }
}
Boost,
Thread,
Reply,
+ Vote,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
last_size: Option<(usize, usize)>,
ui_mode: UIMode,
selection: Option<(isize, usize)>,
+ selection_restrict_to_item: Option<isize>,
select_aux: Option<bool>, // distinguishes fave from unfave, etc
+ poll_options_selected: HashSet<usize>,
file_desc: Type,
search_direction: Option<SearchDirection>,
last_search: Option<Regex>,
last_size: None,
ui_mode: UIMode::Normal,
selection: None,
+ selection_restrict_to_item: None,
select_aux: None,
+ poll_options_selected: HashSet::new(),
file_desc,
search_direction: None,
last_search: None,
_ => None,
};
+ let poll_options =
+ if self.selection_restrict_to_item == Some(index) {
+ Some(&self.poll_options_selected)
+ } else {
+ None
+ };
+
for line in self.contents.get(index)
- .render_highlighted(w, highlight)
+ .render_highlighted(w, highlight, poll_options)
{
for frag in line.split(w) {
lines.push(frag.to_owned());
fn change_selection_to(&mut self, new_selection: Option<(isize, usize)>,
none_ok: bool, client: &mut Client) -> LogicalAction
{
+ if self.selection_restrict_to_item.is_some_and(|restricted|
+ new_selection.is_some_and(|(item, _)| item != restricted)) {
+ return LogicalAction::Beep;
+ }
+
let result = if new_selection.is_some() {
self.rerender_selected_item();
self.selection = new_selection;
self.change_selection_to(new_selection, false, client)
}
+ fn vote(&mut self) -> LogicalAction {
+ let (item, sub) = self.selection.expect(
+ "we should only call this if we have a selection");
+ self.selection_restrict_to_item = Some(item);
+ if self.contents.get(item).is_multiple_choice_poll() {
+ if self.poll_options_selected.contains(&sub) {
+ self.poll_options_selected.remove(&sub);
+ } else {
+ self.poll_options_selected.insert(sub);
+ }
+ } else {
+ self.poll_options_selected.clear();
+ self.poll_options_selected.insert(sub);
+ }
+ dbg!(&self.poll_options_selected);
+ self.rerender_selected_item();
+ self.ensure_enough_rendered();
+ LogicalAction::Nothing
+ }
+
fn end_selection(&mut self) {
if self.selection.is_some() {
self.rerender_selected_item();
self.selection = None;
self.ensure_enough_rendered();
}
+ self.poll_options_selected.clear();
self.ui_mode = UIMode::Normal;
}
UtilityActivity::ComposeReply(id).into()),
SelectionPurpose::Thread => LogicalAction::Goto(
UtilityActivity::ThreadFile(id, alt).into()),
+ SelectionPurpose::Vote => {
+ match client.vote_in_poll(
+ &id, self.poll_options_selected.iter().copied()) {
+ Ok(_) => {
+ self.contents.update_items(client);
+ LogicalAction::Nothing
+ }
+ Err(_) => LogicalAction::Beep,
+ }
+ }
}
} else {
LogicalAction::Beep
} else {
fs
};
+ let fs = if Type::Item::can_highlight(
+ HighlightType::PollOption)
+ {
+ fs.add(Ctrl('V'), "Vote", 10)
+ } else {
+ fs
+ };
let fs = fs
.add(Pr('/'), "Search Down", 20)
.add(Pr('\\'), "Search Up", 20)
fs.add(Space, "Thread Context", 98)
.add(Pr('F'), "Full Thread", 97)
}
+ SelectionPurpose::Vote => {
+ // Different verb for selecting items
+ // depending on whether the vote lets you
+ // select more than one
+ let verb = if self.contents.get(item)
+ .is_multiple_choice_poll()
+ { "Toggle" } else { "Select" };
+
+ // If you've selected nothing yet, prioritise
+ // the keypress for selecting something.
+ // Otherwise, prioritise the one for
+ // submitting your answer.
+ if self.poll_options_selected.is_empty() {
+ fs.add(Space, verb, 98)
+ .add(Ctrl('V'), "Submit Vote", 97)
+ } else {
+ fs.add(Space, verb, 97)
+ .add(Ctrl('V'), "Submit Vote", 98)
+ }
+ }
};
fs.add(Pr('+'), "Down", 99)
.add(Pr('-'), "Up", 99)
}
}
+ Ctrl('V') => {
+ if Type::Item::can_highlight(HighlightType::PollOption) {
+ self.start_selection(HighlightType::PollOption,
+ SelectionPurpose::Vote,
+ client)
+ } else {
+ LogicalAction::Nothing
+ }
+ }
+
Pr('l') | Pr('L') => {
if Type::CAN_LIST != CanList::Nothing {
self.ui_mode = UIMode::ListSubmenu;
_ => LogicalAction::Nothing,
}
UIMode::Select(_, purpose) => match key {
- Space => self.complete_selection(client, false),
+ Space => match purpose {
+ SelectionPurpose::Vote => self.vote(),
+ _ => self.complete_selection(client, false),
+ }
Pr('d') | Pr('D') => match purpose {
SelectionPurpose::Favourite | SelectionPurpose::Boost =>
self.complete_selection(client, true),
self.complete_selection(client, true),
_ => LogicalAction::Nothing,
}
+ Ctrl('V') => match purpose {
+ SelectionPurpose::Vote =>
+ self.complete_selection(client, false),
+ _ => LogicalAction::Nothing,
+ }
Pr('-') | Up => self.selection_up(client),
Pr('+') | Down => self.selection_down(client),
Pr('q') | Pr('Q') => self.abort_selection(),
#[cfg(test)]
use chrono::NaiveDateTime;
use core::cmp::{min, max};
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
use unicode_width::UnicodeWidthStr;
use super::html;
use super::coloured_string::{ColouredString, ColouredStringSlice};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum HighlightType { User, Status, WholeStatus }
+pub enum HighlightType { User, Status, WholeStatus, PollOption }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Highlight(pub HighlightType, pub usize);
pub trait TextFragment {
fn render(&self, width: usize) -> Vec<ColouredString> {
- self.render_highlighted(width, None)
+ self.render_highlighted(width, None, None)
}
fn can_highlight(_htype: HighlightType) -> bool where Self : Sized {
fn highlighted_id(&self, _highlight: Option<Highlight>) -> Option<String> {
None
}
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>;
+ fn is_multiple_choice_poll(&self) -> bool { false }
+
fn render_highlighted_update(
- &self, width: usize, highlight: &mut Option<Highlight>)
+ &self, width: usize, highlight: &mut Option<Highlight>,
+ poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let (new_highlight, text) = match *highlight {
Some(Highlight(htype, index)) => {
let count = self.count_highlightables(htype);
if index < count {
- (None, self.render_highlighted(width, *highlight))
+ (None, self.render_highlighted(
+ width, *highlight, poll_options))
} else {
(Some(Highlight(htype, index - count)),
- self.render_highlighted(width, None))
+ self.render_highlighted(width, None, poll_options))
}
}
None => {
- (None, self.render_highlighted(width, None))
+ (None, self.render_highlighted(width, None, poll_options))
}
};
*highlight = new_highlight;
None => 0,
}
}
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString> {
match self {
- Some(ref inner) => inner.render_highlighted(width, highlight),
+ Some(ref inner) => inner.render_highlighted(
+ width, highlight, poll_options),
None => Vec::new(),
}
}
fn count_highlightables(&self, htype: HighlightType) -> usize {
self.iter().map(|x| x.count_highlightables(htype)).sum()
}
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString> {
let mut highlight = highlight;
+ // Ignore poll_options, because that's only used when we're
+ // rendering a single item containing the poll in question
itertools::concat(self.iter().map(
- |x| x.render_highlighted_update(width, &mut highlight)))
+ |x| x.render_highlighted_update(width, &mut highlight, None)))
}
fn highlighted_id(&self, highlight: Option<Highlight>) -> Option<String> {
let mut highlight = highlight;
}
impl TextFragment for BlankLine {
- fn render_highlighted(&self, _width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, _width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
Self::render_static()
}
impl TextFragment for SeparatorLine {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut suffix = ColouredString::plain("");
}
impl TextFragment for EditorHeaderSeparator {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
vec! {
}
impl TextFragment for UsernameHeader {
- fn render_highlighted(&self, _width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, _width: usize, highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let header = ColouredString::plain(&format!("{}: ", self.header));
}
impl TextFragment for Paragraph {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut lines = Vec::new();
}
impl TextFragment for FileHeader {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let elephants = width >= 16;
}
impl TextFragment for Html {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
match self {
}
impl TextFragment for ExtendableIndicator {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let message = if self.primed {
}
impl TextFragment for InReplyToLine {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let rendered_para = self.para.render(width - min(width, 3));
}
impl TextFragment for VisibilityLine {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let line = match self.vis {
}
impl TextFragment for NotificationLog {
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut full_para = Paragraph::new().set_indent(0, 2);
}
impl TextFragment for UserListEntry {
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut para = Paragraph::new().set_indent(0, 2);
}
impl TextFragment for Media {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut lines: Vec<_> = ColouredString::uniform(&self.url, 'M')
}
impl TextFragment for FileStatusLineFinal {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut line = ColouredString::plain("");
}
impl TextFragment for MenuKeypressLine {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let ourwidth = self.lmaxwid + self.rmaxwid + 3; // " = " in the middle
}
impl TextFragment for CentredInfoLine {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let twidth = width.saturating_sub(1);
}
}
+struct Poll {
+ title: Paragraph,
+ options: Vec<(bool, ColouredString)>,
+ id: String,
+ eligible: bool,
+ multiple: bool,
+}
+
pub struct StatusDisplay {
sep: SeparatorLine,
from: UsernameHeader,
vis: Option<VisibilityLine>,
content: Html,
media: Vec<Media>,
+ poll: Option<Poll>,
blank: BlankLine,
id: String,
}
Media::new(&m.url, desc_ref)
}).collect();
+ let poll = st.poll.map(|poll| {
+ let mut extras = Vec::new();
+ let voters = poll.voters_count.unwrap_or(poll.votes_count);
+ if poll.multiple {
+ extras.push(ColouredString::uniform("multiple choice", 'K'));
+ }
+ if poll.expired {
+ extras.push(ColouredString::uniform("expired", 'H'));
+ extras.push(ColouredString::uniform(
+ &format!("{} voters", voters), 'H'));
+ } else {
+ if let Some(date) = poll.expires_at {
+ extras.push(ColouredString::uniform(
+ &format!("expires {}", format_date(date)), 'H'));
+ } else {
+ extras.push(ColouredString::uniform("no expiry", 'H'));
+ }
+ extras.push(ColouredString::uniform(
+ &format!("{} voters so far", voters), 'H'));
+ }
+ let mut desc = ColouredString::uniform("Poll: ", 'H');
+ for (i, extra) in extras.iter().enumerate() {
+ if i > 0 {
+ desc.push_str(&ColouredString::uniform(", ", 'H').slice());
+ }
+ desc.push_str(&extra.slice());
+ }
+
+ let title = Paragraph::new().set_indent(0, 2).add(&desc);
+
+ let mut options = Vec::new();
+ for (thisindex, opt) in poll.options.iter().enumerate() {
+ let voted = poll.own_votes.as_ref().map_or(false, |votes| {
+ votes.iter().any(|i| *i == thisindex)
+ });
+ let mut desc = ColouredString::plain(" ");
+ desc.push_str(&ColouredString::plain(&opt.title).slice());
+ if let Some(n) = opt.votes_count {
+ desc.push_str(&ColouredString::uniform(
+ &format!(" ({})", n), 'H').slice());
+ }
+ options.push((voted, desc));
+ }
+
+ Poll {
+ title,
+ options,
+ id: poll.id.clone(),
+ eligible: poll.voted != Some(true) && !poll.expired,
+ multiple: poll.multiple,
+ }
+ });
+
StatusDisplay {
sep,
from,
vis,
content,
media,
+ poll,
blank: BlankLine::new(),
id: st.id,
}
}
impl TextFragment for StatusDisplay {
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ poll_options_selected: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut lines = Vec::new();
push_fragment(&mut lines, self.sep.render(width));
push_fragment(&mut lines, self.from.render_highlighted_update(
- width, &mut highlight));
+ width, &mut highlight, None));
push_fragment(&mut lines, self.via.render_highlighted_update(
- width, &mut highlight));
+ width, &mut highlight, None));
push_fragment(&mut lines, self.vis.render(width));
push_fragment(&mut lines, self.irt.render(width));
push_fragment(&mut lines, self.blank.render(width));
push_fragment_opt_highlight(&mut lines, m.render(width));
push_fragment_opt_highlight(&mut lines, self.blank.render(width));
}
+ if let Some(poll) = &self.poll {
+ push_fragment_opt_highlight(&mut lines, poll.title.render(width));
+ for (i, (voted, desc)) in poll.options.iter().enumerate() {
+ let highlighting_this_option =
+ highlight.consume(HighlightType::PollOption, 1) == Some(0);
+ let voted = poll_options_selected.map_or(
+ *voted, |opts| opts.contains(&i));
+ let option = Paragraph::new().set_indent(0, 2).add(
+ &ColouredString::uniform(
+ if voted {"[X]"} else {"[ ]"}, 'H'))
+ .add(&desc);
+ let push_fragment_opt_highlight = if highlighting_this_option {
+ push_fragment_highlighted
+ } else {
+ push_fragment_opt_highlight
+ };
+ push_fragment_opt_highlight(&mut lines, option.render(width));
+ }
+ push_fragment_opt_highlight(&mut lines, self.blank.render(width));
+ }
lines
}
fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
- htype == HighlightType::User || htype == HighlightType::Status ||
- htype == HighlightType::WholeStatus
+ htype == HighlightType::User ||
+ htype == HighlightType::Status ||
+ htype == HighlightType::WholeStatus ||
+ htype == HighlightType::PollOption
}
fn count_highlightables(&self, htype: HighlightType) -> usize {
}
HighlightType::Status => 1,
HighlightType::WholeStatus => 1,
+
+ HighlightType::PollOption => self.poll.as_ref()
+ .filter(|poll| poll.eligible)
+ .map_or(0, |poll| poll.options.len()),
}
}
}
Some(Highlight(HighlightType::WholeStatus, 0)) |
Some(Highlight(HighlightType::Status, 0)) => Some(self.id.clone()),
+ Some(Highlight(HighlightType::PollOption, i)) => self.poll.as_ref()
+ .filter(|poll| poll.eligible)
+ .filter(|poll| i < poll.options.len())
+ .map(|poll| poll.id.clone()),
_ => None,
}
}
+
+ fn is_multiple_choice_poll(&self) -> bool {
+ self.poll.as_ref().map_or(false, |poll| poll.multiple)
+ }
}
pub struct DetailedStatusDisplay {
}
impl TextFragment for DetailedStatusDisplay {
- fn render_highlighted(&self, width: usize, highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, highlight: Option<Highlight>,
+ poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut lines = Vec::new();
let mut highlight = highlight;
push_fragment(&mut lines, self.sd.render_highlighted_update(
- width, &mut highlight));
+ width, &mut highlight, poll_options));
push_fragment(&mut lines, self.sep.render(width));
push_fragment(&mut lines, self.blank.render(width));
}
fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
- htype == HighlightType::User || htype == HighlightType::Status
+ htype == HighlightType::User || htype == HighlightType::Status ||
+ htype == HighlightType::PollOption
}
fn count_highlightables(&self, htype: HighlightType) -> usize {
}
None
}
+
+ fn is_multiple_choice_poll(&self) -> bool {
+ self.sd.is_multiple_choice_poll()
+ }
}
pub struct ExamineUserDisplay {
}
impl TextFragment for ExamineUserDisplay {
- fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>)
+ fn render_highlighted(&self, width: usize, _highlight: Option<Highlight>,
+ _poll_options: Option<&HashSet<usize>>)
-> Vec<ColouredString>
{
let mut lines = Vec::new();