jk_core/command_history/identity.rs
1use std::ffi::OsString;
2use std::path::PathBuf;
3
4use super::redaction::{redact_argv, redact_text};
5use crate::command::preview_argv;
6use crate::{ExecutionMode, GlobalOptions, JjCommandSpec};
7
8/// Command data captured from a typed command spec.
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct CommandIdentity {
11 /// Exact process argv after global options are applied, excluding the `jj` binary.
12 pub argv: Vec<OsString>,
13 /// Display-only preview from the command spec.
14 pub spec_preview: String,
15 /// Broad command family for filtering and grouping.
16 pub command_family: CommandFamily,
17 /// Human-readable title from the command spec.
18 pub title: String,
19}
20
21impl CommandIdentity {
22 /// Captures command identity from a typed command spec.
23 #[must_use]
24 pub fn from_spec(spec: &JjCommandSpec) -> Self {
25 Self {
26 argv: redact_argv(spec.process_argv()),
27 spec_preview: redact_text(&spec.preview()).0,
28 command_family: CommandFamily::from_spec(spec),
29 title: redact_text(spec.title()).0,
30 }
31 }
32
33 /// Returns the exact redacted process command line captured for this command.
34 #[must_use]
35 pub fn process_preview(&self) -> String {
36 preview_argv(&self.argv)
37 }
38}
39
40/// Broad command family for history filtering.
41#[derive(Clone, Debug, Eq, PartialEq)]
42#[non_exhaustive]
43pub enum CommandFamily {
44 /// Bare `jj` or configured default command.
45 JjDefault,
46 /// `jj log`.
47 JjLog,
48 /// `jj diff`.
49 JjDiff,
50 /// `jj show`.
51 JjShow,
52 /// `jj status`.
53 JjStatus,
54 /// `jj describe`.
55 JjDescribe,
56 /// `jj new`.
57 JjNew,
58 /// `jj edit`.
59 JjEdit,
60 /// `jj evolog`.
61 JjEvolog,
62 /// `jj workspace ...`.
63 JjWorkspace,
64 /// Future `jj op ...`.
65 JjOperation,
66 /// Future user-entered `:` command.
67 UserJjCommand,
68 /// Future foreground external command.
69 ExternalCommand,
70 /// A command family not yet modeled.
71 Other(String),
72}
73
74impl CommandFamily {
75 fn from_spec(spec: &JjCommandSpec) -> Self {
76 if spec.mode() == ExecutionMode::CommandMode {
77 return Self::UserJjCommand;
78 }
79
80 let Some(command) = spec.argv().first() else {
81 return Self::JjDefault;
82 };
83 match command.to_string_lossy().as_ref() {
84 "log" => Self::JjLog,
85 "diff" => Self::JjDiff,
86 "show" => Self::JjShow,
87 "status" => Self::JjStatus,
88 "describe" => Self::JjDescribe,
89 "new" => Self::JjNew,
90 "edit" => Self::JjEdit,
91 "evolog" => Self::JjEvolog,
92 "workspace" => Self::JjWorkspace,
93 "op" => Self::JjOperation,
94 other => Self::Other(other.to_owned()),
95 }
96 }
97}
98
99/// The view and action that caused a command.
100#[derive(Clone, Debug, Eq, PartialEq)]
101pub struct CommandSource {
102 /// Source view that owned the command action.
103 pub view: SourceView,
104 /// Source action that triggered the command.
105 pub action: SourceAction,
106 /// Optional key binding or command key.
107 pub key: Option<String>,
108}
109
110impl CommandSource {
111 /// Creates a command source without a key binding.
112 #[must_use]
113 pub const fn new(view: SourceView, action: SourceAction) -> Self {
114 Self {
115 view,
116 action,
117 key: None,
118 }
119 }
120
121 /// Adds the key binding that triggered this source action.
122 #[must_use]
123 pub fn with_key(mut self, key: impl Into<String>) -> Self {
124 self.key = Some(key.into());
125 self
126 }
127}
128
129/// App view that triggered a command.
130#[derive(Clone, Debug, Eq, PartialEq)]
131#[non_exhaustive]
132pub enum SourceView {
133 /// Log view.
134 Log,
135 /// Diff view.
136 Diff,
137 /// Show view.
138 Show,
139 /// Status view.
140 Status,
141 /// Evolog view.
142 Evolog,
143 /// Workspaces list.
144 Workspaces,
145 /// Selected workspace log view.
146 WorkspaceLog,
147 /// Selected workspace status view.
148 WorkspaceStatus,
149 /// Selected workspace diff view.
150 WorkspaceDiff,
151 /// Command history view.
152 CommandHistory,
153 /// Operation log view.
154 OperationLog,
155 /// Operation show view.
156 OperationShow,
157 /// Operation diff view.
158 OperationDiff,
159 /// A source view not yet modeled.
160 Other(String),
161}
162
163/// App action that triggered a command.
164#[derive(Clone, Debug, Eq, PartialEq)]
165#[non_exhaustive]
166pub enum SourceAction {
167 /// Initial view load.
168 InitialLoad,
169 /// Explicit refresh.
170 Refresh,
171 /// Open a diff.
172 OpenDiff,
173 /// Open a show view.
174 OpenShow,
175 /// Open a status view.
176 OpenStatus,
177 /// Open an evolog view.
178 OpenEvolog,
179 /// Describe the selected revision.
180 DescribeRevision,
181 /// Abandon the selected revision.
182 AbandonRevision,
183 /// Create a new change from the selected revision.
184 NewRevision,
185 /// Move the working copy to the selected revision.
186 EditRevision,
187 /// List workspaces.
188 WorkspaceList,
189 /// Show selected workspace status.
190 WorkspaceStatus,
191 /// Show selected workspace log.
192 WorkspaceLog,
193 /// Show selected workspace diff.
194 WorkspaceDiff,
195 /// Run selected workspace update-stale.
196 WorkspaceUpdateStale,
197 /// List repository operations.
198 OperationLog,
199 /// Show a selected operation.
200 OperationShow,
201 /// Diff a selected operation.
202 OperationDiff,
203 /// Undo the latest operation.
204 Undo,
205 /// Redo the latest undone operation.
206 Redo,
207 /// Run a user-entered `jj` command.
208 UserJjCommand,
209 /// A source action not yet modeled.
210 Other(String),
211}
212
213/// Context captured when a command starts.
214#[derive(Clone, Debug, Eq, PartialEq)]
215pub struct CommandExecutionContext {
216 /// Process working directory, if set by the command spec.
217 pub cwd: Option<PathBuf>,
218 /// Repository path from global `jj` options, if set.
219 pub repository: Option<PathBuf>,
220 /// Redacted snapshot of global `jj` options.
221 pub global_options: GlobalOptionsSnapshot,
222}
223
224impl CommandExecutionContext {
225 /// Captures execution context from a typed command spec.
226 #[must_use]
227 pub fn from_spec(spec: &JjCommandSpec) -> Self {
228 Self {
229 cwd: spec.cwd().map(PathBuf::from),
230 repository: spec.repository().map(PathBuf::from),
231 global_options: GlobalOptionsSnapshot::from_global_options(spec.global_options()),
232 }
233 }
234}
235
236/// Redacted global `jj` options retained by command history.
237#[derive(Clone, Debug, Eq, PartialEq)]
238pub struct GlobalOptionsSnapshot {
239 /// Global argv rendered before the command family, excluding the `jj` binary.
240 pub argv: Vec<OsString>,
241}
242
243impl GlobalOptionsSnapshot {
244 /// Captures redacted global argv from typed global options.
245 #[must_use]
246 pub fn from_global_options(options: &GlobalOptions) -> Self {
247 Self {
248 argv: redact_argv(options.argv()),
249 }
250 }
251}