editor_types/application.rs
1//! # Application Customization
2//!
3//! ## Overview
4//!
5//! This module contains traits that library consumers can use for extending modalkit to better fit
6//! their own needs.
7//!
8//! ## Example
9//!
10//! ```
11//! use std::fmt;
12//! use std::path::PathBuf;
13//!
14//! use editor_types::{
15//! application::{
16//! ApplicationAction,
17//! ApplicationError,
18//! ApplicationWindowId,
19//! ApplicationContentId,
20//! ApplicationInfo,
21//! ApplicationStore,
22//! },
23//! context::EditContext,
24//! prelude::*,
25//! };
26//! use keybindings::SequenceStatus;
27//!
28//! // Unique identifier for a review.
29//! #[derive(Clone, Debug, Eq, Hash, PartialEq)]
30//! struct ReviewId(usize);
31//!
32//! // Unique identifier for a user.
33//! #[derive(Clone, Debug, Eq, Hash, PartialEq)]
34//! struct UserId(usize);
35//!
36//! #[derive(Clone, Debug, Eq, PartialEq)]
37//! enum CodeReviewAction {
38//! // Approve a review for merging.
39//! Approve(ReviewId),
40//!
41//! // Leave a comment on a line in a file in a review.
42//! Comment(ReviewId, PathBuf, usize, String),
43//!
44//! // Show more lines around the hunk.
45//! ExpandHunk(Count),
46//!
47//! // Merge changes after review and approval.
48//! Merge(ReviewId),
49//! }
50//!
51//! impl ApplicationAction for CodeReviewAction {
52//! fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus {
53//! match self {
54//! CodeReviewAction::Approve(..) => SequenceStatus::Break,
55//! CodeReviewAction::Comment(..) => SequenceStatus::Break,
56//! CodeReviewAction::ExpandHunk(..) => SequenceStatus::Atom,
57//! CodeReviewAction::Merge(..) => SequenceStatus::Break,
58//! }
59//! }
60//!
61//! fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
62//! match self {
63//! CodeReviewAction::Approve(..) => SequenceStatus::Atom,
64//! CodeReviewAction::Comment(..) => SequenceStatus::Atom,
65//! CodeReviewAction::ExpandHunk(..) => SequenceStatus::Atom,
66//! CodeReviewAction::Merge(..) => SequenceStatus::Atom,
67//! }
68//! }
69//!
70//! fn is_last_selection(&self, _: &EditContext) -> SequenceStatus {
71//! match self {
72//! CodeReviewAction::Approve(..) => SequenceStatus::Ignore,
73//! CodeReviewAction::Comment(..) => SequenceStatus::Ignore,
74//! CodeReviewAction::ExpandHunk(..) => SequenceStatus::Ignore,
75//! CodeReviewAction::Merge(..) => SequenceStatus::Ignore,
76//! }
77//! }
78//!
79//! fn is_switchable(&self, _: &EditContext) -> bool {
80//! match self {
81//! CodeReviewAction::Approve(..) => false,
82//! CodeReviewAction::Comment(..) => false,
83//! CodeReviewAction::ExpandHunk(..) => false,
84//! CodeReviewAction::Merge(..) => false,
85//! }
86//! }
87//! }
88//!
89//! struct CodeReviewStore {
90//! user: UserId,
91//! }
92//!
93//! impl ApplicationStore for CodeReviewStore {}
94//!
95//! #[derive(Clone, Debug, Eq, Hash, PartialEq)]
96//! enum CodeReviewWindowId {
97//! // A window that shows a code review.
98//! Review(ReviewId),
99//!
100//! // A window that shows a user's open reviews.
101//! User(UserId),
102//! }
103//!
104//! impl ApplicationWindowId for CodeReviewWindowId {}
105//!
106//! #[derive(Clone, Debug, Eq, Hash, PartialEq)]
107//! enum CodeReviewContentId {
108//! // Different buffer used by the command bar.
109//! Command(CommandType),
110//!
111//! // Buffer for a comment left on a line.
112//! Review(ReviewId, usize),
113//! }
114//!
115//! impl ApplicationContentId for CodeReviewContentId {}
116//!
117//! #[derive(Debug)]
118//! enum CodeReviewError {
119//! NoReview(ReviewId),
120//! }
121//!
122//! impl fmt::Display for CodeReviewError {
123//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124//! match self {
125//! CodeReviewError::NoReview(id) => write!(f, "No review with ID {:?}", id),
126//! }
127//! }
128//! }
129//!
130//! impl ApplicationError for CodeReviewError {}
131//!
132//! #[derive(Clone, Debug, Eq, PartialEq)]
133//! enum CodeReviewInfo {}
134//!
135//! impl ApplicationInfo for CodeReviewInfo {
136//! type Error = CodeReviewError;
137//! type Action = CodeReviewAction;
138//! type Store = CodeReviewStore;
139//! type WindowId = CodeReviewWindowId;
140//! type ContentId = CodeReviewContentId;
141//!
142//! fn content_of_command(ct: CommandType) -> CodeReviewContentId {
143//! CodeReviewContentId::Command(ct)
144//! }
145//! }
146//! ```
147use std::fmt::{Debug, Display};
148use std::hash::Hash;
149
150use crate::context::EditContext;
151use crate::prelude::CommandType;
152use keybindings::SequenceStatus;
153
154/// Trait for objects that describe application-specific actions.
155///
156/// Implementors of this trait can be used with [Action::Application]. This can then be used to
157/// create additional keybindings and commands on top of the more editing-specific ones provided
158/// by this crate.
159///
160/// [Action::Application]: crate::Action::Application
161pub trait ApplicationAction: Clone + Debug + Eq + PartialEq + Send {
162 /// Allows controlling how application-specific actions are included in
163 /// [RepeatType::EditSequence](crate::prelude::RepeatType::EditSequence).
164 fn is_edit_sequence(&self, ctx: &EditContext) -> SequenceStatus;
165
166 /// Allows controlling how application-specific actions are included in
167 /// [RepeatType::LastAction](crate::prelude::RepeatType::LastAction).
168 fn is_last_action(&self, ctx: &EditContext) -> SequenceStatus;
169
170 /// Allows controlling how application-specific actions are included in
171 /// [RepeatType::LastSelection](crate::prelude::RepeatType::LastSelection).
172 fn is_last_selection(&self, ctx: &EditContext) -> SequenceStatus;
173
174 /// Allows controlling whether an application-specific action should be retried in
175 /// a new buffer when the currently targeted buffer returns a `WrongBuffer`-style error.
176 ///
177 /// For example, jumping to a mark in a different buffer when the current one doesn't
178 /// contain the mark.
179 fn is_switchable(&self, ctx: &EditContext) -> bool;
180}
181
182impl ApplicationAction for () {
183 fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus {
184 SequenceStatus::Break
185 }
186
187 fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
188 SequenceStatus::Ignore
189 }
190
191 fn is_last_selection(&self, _: &EditContext) -> SequenceStatus {
192 SequenceStatus::Ignore
193 }
194
195 fn is_switchable(&self, _: &EditContext) -> bool {
196 false
197 }
198}
199
200/// Trait for application-specific errors.
201pub trait ApplicationError: Debug + Display {}
202
203impl ApplicationError for String {}
204
205/// Trait for objects that hold application-specific information.
206pub trait ApplicationStore {}
207
208impl ApplicationStore for () {}
209
210/// Trait for window identifiers in an application.
211pub trait ApplicationWindowId: Clone + Debug + Eq + Hash + Send {}
212
213impl ApplicationWindowId for () {}
214impl ApplicationWindowId for usize {}
215impl ApplicationWindowId for Option<usize> {}
216impl ApplicationWindowId for String {}
217
218/// Trait for identifiers of specific content within a window in an application.
219pub trait ApplicationContentId: Clone + Debug + Eq + Hash + Send {}
220
221impl ApplicationContentId for () {}
222impl ApplicationContentId for usize {}
223impl ApplicationContentId for Option<usize> {}
224impl ApplicationContentId for String {}
225
226/// Trait for objects that describe application-specific behaviour and types.
227#[allow(unused)]
228pub trait ApplicationInfo: Clone + Debug + Eq + PartialEq {
229 /// An application-specific error type.
230 type Error: ApplicationError;
231
232 /// The type for application-specific actions.
233 type Action: ApplicationAction;
234
235 /// The type for application-specific storage.
236 type Store: ApplicationStore;
237
238 /// The type for application-specific windows.
239 type WindowId: ApplicationWindowId;
240
241 /// The type for application-specific content within a window.
242 type ContentId: ApplicationContentId;
243
244 /// Get the [ApplicationContentId] used to show a given command type.
245 fn content_of_command(cmdtype: CommandType) -> Self::ContentId;
246}
247
248/// A default implementor of [ApplicationInfo] for consumers that don't require any customization.
249#[derive(Clone, Debug, Eq, PartialEq)]
250pub enum EmptyInfo {}
251
252impl ApplicationInfo for EmptyInfo {
253 type Error = String;
254 type Action = ();
255 type Store = ();
256 type WindowId = String;
257 type ContentId = String;
258
259 fn content_of_command(cmdtype: CommandType) -> String {
260 match cmdtype {
261 CommandType::Search => "*search*".into(),
262 CommandType::Command => "*command*".into(),
263 }
264 }
265}