Skip to main content

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}