1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! How the screen is shaped: cursors, filters, what is open over the content.
use std::collections::HashSet;
use std::time::Instant;
use super::{Input, InputMode, IssueList, IssueLists, NewIssueForm, PerSource, Popup, ViewKind};
use crate::api::ids::{FavoriteId, IssueId};
use crate::api::types::Issue;
use crate::config::Config;
use crate::grouping::GroupBy;
#[derive(Debug)]
pub struct ViewState {
pub input_mode: InputMode,
pub popup: Popup,
/// The highlighted row of the open popup, counted in what its query
/// leaves — see `App::popup_rows`.
pub popup_index: usize,
/// What has been typed into the open popup to narrow it.
pub popup_query: Input,
/// Whether the open popup was reached from the command palette, so Esc
/// steps back there instead of closing.
pub popup_from_palette: bool,
/// How each of the five issue lists is shaped.
pub lists: IssueLists,
pub group_by: GroupBy,
/// Group keys the user has folded away.
pub collapsed_groups: HashSet<String>,
/// Which tab the Views pages show.
pub view_kind: ViewKind,
pub selected_view_index: usize,
pub selected_view_project_index: usize,
pub selected_project_index: usize,
pub selected_cycle_index: usize,
pub sidebar: Sidebar,
pub palette: Palette,
pub detail_scroll: u16,
pub comment: Input,
/// Notes collected for the agent, oldest first.
pub notes: Vec<super::Note>,
/// The note being typed, and the issue it is about.
pub note: Input,
pub note_about: Option<(String, String)>,
/// Draft issue, present only while the create form is open.
pub new_issue: Option<NewIssueForm>,
/// First key of a pending multi-key chord (Linear's `g …` sequences).
pub pending_chord: Option<char>,
pub show_help: bool,
pub help_scroll: u16,
pub status_message: Option<String>,
pub error_popup: Option<String>,
pub spinner_frame: usize,
}
#[derive(Debug, Default)]
pub struct Palette {
pub query: Input,
/// The highlighted entry, as an index into what the query matches.
pub selected: usize,
/// The issue under the cursor when the palette opened. A command runs on
/// that issue or not at all — a page landing under the palette can move
/// the cursor to another one.
pub issue: Option<IssueId>,
/// Commands run from the palette, most recent first, by title.
pub recent: Vec<&'static str>,
/// Issues Linear's search returned for the query.
pub results: Vec<Issue>,
/// True while a search for the query is in flight.
pub searching: bool,
/// When the query has rested long enough to be searched.
pub search_due: Option<Instant>,
/// Which search `results` must answer; older answers are dropped.
pub seq: u64,
}
#[derive(Debug)]
pub struct Sidebar {
pub visible: bool,
pub width: u16,
/// True while the cursor is in the sidebar rather than the content pane.
pub focus: bool,
pub index: usize,
/// Favorites folders the user has folded, by favorite id.
pub collapsed_folders: HashSet<FavoriteId>,
}
impl ViewState {
pub fn new(config: &Config) -> Self {
Self {
input_mode: InputMode::Normal,
popup: Popup::None,
popup_index: 0,
popup_query: Input::default(),
popup_from_palette: false,
lists: PerSource::from_fn(IssueList::new),
group_by: GroupBy::from_config(config.ui.group_by),
collapsed_groups: HashSet::new(),
view_kind: ViewKind::default(),
selected_view_index: 0,
selected_view_project_index: 0,
selected_project_index: 0,
selected_cycle_index: 0,
sidebar: Sidebar {
visible: config.ui.sidebar,
width: config.ui.sidebar_width,
focus: false,
index: 0,
collapsed_folders: HashSet::new(),
},
palette: Palette::default(),
detail_scroll: 0,
comment: Input::default(),
notes: Vec::new(),
note: Input::default(),
note_about: None,
new_issue: None,
pending_chord: None,
show_help: false,
help_scroll: 0,
status_message: None,
error_popup: None,
spinner_frame: 0,
}
}
}